Séparer les workflows d'intervention et de prévention
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
fe7deff504
commit
701a765576
11 changed files with 96 additions and 8 deletions
|
|
@ -116,6 +116,20 @@ WORK_TYPES = {
|
|||
"curative": "Intervention curative",
|
||||
}
|
||||
|
||||
WORKFLOW_TYPES = {
|
||||
"corrective": "Corrective",
|
||||
"preventive": "Préventive",
|
||||
"travaux": "Travaux",
|
||||
"prevention": "Prévention",
|
||||
}
|
||||
|
||||
WORKFLOW_STATUS_ORDERS = {
|
||||
"corrective": ["brouillon", "en_attente", "planifiee", "en_cours", "en_attente_pieces", "en_attente_entreprise", "terminee", "cloturee"],
|
||||
"preventive": ["brouillon", "planifiee", "en_cours", "en_attente_pieces", "en_attente_entreprise", "terminee", "cloturee"],
|
||||
"travaux": ["brouillon", "attente_chef", "demande_departement", "attente_devis", "devis_a_valider", "devis_accepte", "entreprise_mandatee", "travaux_planifies", "travaux_en_cours", "travaux_termines", "reception", "cloturee"],
|
||||
"prevention": ["brouillon", "en_attente", "planifiee", "en_cours", "terminee", "cloturee"],
|
||||
}
|
||||
|
||||
# Rôles utilisateurs
|
||||
ROLES = {
|
||||
"admin": "Administrateur",
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ class Intervention(db.Model):
|
|||
|
||||
# Statut & Workflow
|
||||
status = db.Column(db.String(30), default="brouillon")
|
||||
workflow_type = db.Column(db.String(20), nullable=False, default="corrective", index=True)
|
||||
|
||||
# Responsable actuel (qui doit agir maintenant)
|
||||
assigned_to_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
|
||||
|
|
@ -173,6 +174,11 @@ class Intervention(db.Model):
|
|||
}
|
||||
return colors.get(self.status, 'secondary')
|
||||
|
||||
@property
|
||||
def workflow_label(self):
|
||||
from app_new.constants import WORKFLOW_TYPES
|
||||
return WORKFLOW_TYPES.get(self.workflow_type, self.workflow_type or 'Corrective')
|
||||
|
||||
@property
|
||||
def status_label(self):
|
||||
"""Label du statut."""
|
||||
|
|
|
|||
|
|
@ -139,6 +139,7 @@ def create_intervention_from_task(id, task_id):
|
|||
equipment_id=id,
|
||||
room_id=request.form.get('room_id') or task.room_id,
|
||||
type='preventif',
|
||||
workflow_type='preventive',
|
||||
status='en_cours',
|
||||
scheduled_date=task.scheduled_date,
|
||||
scheduled_start=request.form.get('scheduled_start') or task.scheduled_start.strftime('%H:%M') if task.scheduled_start else None,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ from ..core.models.planning import ScheduledTask
|
|||
from ..core.models.equipment import Equipment
|
||||
from ..core.models.college import Room
|
||||
from ..core.models.company import Service, Company
|
||||
from app_new.constants import INTERVENTION_STATUSES, INTERVENTION_TRANSITIONS, PRIORITIES
|
||||
from app_new.constants import INTERVENTION_STATUSES, INTERVENTION_TRANSITIONS, PRIORITIES, WORKFLOW_TYPES, WORKFLOW_STATUS_ORDERS
|
||||
|
||||
interventions_bp = Blueprint('interventions', __name__)
|
||||
|
||||
|
|
@ -35,6 +35,7 @@ def index():
|
|||
status = request.args.get('status', '')
|
||||
priority = request.args.get('priority', '')
|
||||
intervention_type = request.args.get('type', '')
|
||||
workflow_type = request.args.get('workflow_type', '')
|
||||
lot_id = request.args.get('lot', type=int)
|
||||
assigned_to_id = request.args.get('assigned_to_id', type=int)
|
||||
frequency = request.args.get('frequency', '')
|
||||
|
|
@ -57,6 +58,8 @@ def index():
|
|||
query = query.filter_by(priority=priority)
|
||||
if intervention_type:
|
||||
query = query.filter(Intervention.type == intervention_type)
|
||||
if workflow_type in WORKFLOW_TYPES:
|
||||
query = query.filter_by(workflow_type=workflow_type)
|
||||
if lot_id:
|
||||
query = query.filter_by(lot_id=lot_id)
|
||||
if assigned_to_id:
|
||||
|
|
@ -106,6 +109,8 @@ def index():
|
|||
lots=lots,
|
||||
users=users,
|
||||
statuses=INTERVENTION_STATUSES,
|
||||
workflow_status_order=WORKFLOW_STATUS_ORDERS.get(intervention.workflow_type, WORKFLOW_STATUS_ORDERS['corrective']),
|
||||
workflow_types=WORKFLOW_TYPES,
|
||||
priorities=PRIORITIES,
|
||||
status_counts=status_counts,
|
||||
trashed_count=trashed_count,
|
||||
|
|
@ -132,6 +137,11 @@ def create():
|
|||
# Si pas de demandeur fourni, utiliser le nom de l'utilisateur connecté
|
||||
requester_name = current_user.username if hasattr(current_user, 'username') else 'Inconnu'
|
||||
|
||||
selected_type = request.form.get('intervention_type', 'curatif')
|
||||
workflow_type = request.form.get('workflow_type') or {
|
||||
'preventif': 'preventive', 'amelioratif': 'travaux',
|
||||
'prevention': 'prevention', 'administratif': 'prevention',
|
||||
}.get(selected_type, 'corrective')
|
||||
intervention = Intervention(
|
||||
title=request.form.get('title'),
|
||||
description=request.form.get('description') or '',
|
||||
|
|
@ -142,7 +152,8 @@ def create():
|
|||
author_id=current_user.id,
|
||||
status=request.form.get('status', 'brouillon'),
|
||||
priority=request.form.get('priority', 'normale'),
|
||||
type=request.form.get('intervention_type', 'curatif'),
|
||||
type=selected_type,
|
||||
workflow_type=workflow_type if workflow_type in WORKFLOW_TYPES else 'corrective',
|
||||
notes=request.form.get('notes') or '',
|
||||
requester_name=requester_name,
|
||||
scheduled_date=datetime.strptime(request.form.get('intervention_date'), '%Y-%m-%d').date() if request.form.get('intervention_date') else None,
|
||||
|
|
@ -211,6 +222,7 @@ def create():
|
|||
'assignee': '',
|
||||
'date': None,
|
||||
'requester_name': ''
|
||||
, 'workflow_type': request.args.get('workflow', 'corrective')
|
||||
}
|
||||
|
||||
interpretation = None
|
||||
|
|
@ -253,6 +265,7 @@ def create():
|
|||
'information': 'curatif'
|
||||
}
|
||||
prefilled['type'] = type_map.get(interpretation.analysis_type, 'curatif')
|
||||
prefilled['workflow_type'] = 'preventive' if interpretation.analysis_type == 'preventive' else prefilled.get('workflow_type', 'corrective')
|
||||
prefilled['equipment'] = interpretation.suggested_equipment or ''
|
||||
else:
|
||||
json_suggestions = {
|
||||
|
|
@ -327,6 +340,7 @@ def create():
|
|||
users=users,
|
||||
statuses=INTERVENTION_STATUSES,
|
||||
priorities=PRIORITIES,
|
||||
workflow_types=WORKFLOW_TYPES,
|
||||
prefilled=prefilled,
|
||||
equipment_id=equipment_id,
|
||||
equipment_match=equipment_match,
|
||||
|
|
@ -453,6 +467,9 @@ def edit(id):
|
|||
intervention.status = new_status
|
||||
intervention.priority = request.form.get('priority')
|
||||
intervention.type = request.form.get('intervention_type', intervention.type)
|
||||
workflow_type = request.form.get('workflow_type')
|
||||
if workflow_type in WORKFLOW_TYPES:
|
||||
intervention.workflow_type = workflow_type
|
||||
intervention.scheduled_date = request.form.get('scheduled_date') or None
|
||||
intervention.notes = request.form.get('notes')
|
||||
intervention.requester_name = request.form.get('requester_name') or None
|
||||
|
|
@ -487,6 +504,7 @@ def edit(id):
|
|||
'assignee': intervention.assigned_to.username if intervention.assigned_to else '',
|
||||
'date': intervention.scheduled_date,
|
||||
'requester_name': intervention.requester_name or ''
|
||||
, 'workflow_type': intervention.workflow_type
|
||||
}
|
||||
|
||||
return render_template('interventions/new.html',
|
||||
|
|
@ -497,6 +515,7 @@ def edit(id):
|
|||
users=users,
|
||||
statuses=INTERVENTION_STATUSES,
|
||||
priorities=PRIORITIES,
|
||||
workflow_types=WORKFLOW_TYPES,
|
||||
prefilled=prefilled,
|
||||
title="Modifier l'intervention")
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,8 @@
|
|||
<button class="btn btn-outline-primary btn-sm" type="submit"><i class="bi bi-arrow-repeat"></i> Appliquer</button>
|
||||
</form>
|
||||
<div class="d-flex flex-wrap align-items-center gap-1" style="font-size:0.75rem;">
|
||||
{% set workflow_order = ['brouillon', 'attente_chef', 'demande_departement', 'attente_devis', 'devis_a_valider', 'devis_accepte', 'entreprise_mandatee', 'travaux_planifies', 'travaux_en_cours', 'travaux_termines', 'reception', 'cloturee'] if intervention.execution_mode != 'interne' else ['brouillon', 'attente_chef', 'en_cours', 'en_attente_pieces', 'en_attente_entreprise', 'terminee', 'cloturee'] %}
|
||||
{% set workflow_order = workflow_status_order %}
|
||||
<div class="mb-2"><span class="badge bg-secondary">Workflow {{ intervention.workflow_label }}</span></div>
|
||||
{% for step in workflow_order %}
|
||||
{% if step in statuses %}
|
||||
{% set is_current = (step == intervention.status) %}
|
||||
|
|
|
|||
|
|
@ -55,6 +55,12 @@
|
|||
</button>
|
||||
</div>
|
||||
<form method="GET" class="row g-2 align-items-end collapse d-md-flex" id="filterCollapse">
|
||||
<div class="col-6 col-md-2">
|
||||
<select name="workflow_type" class="form-select form-select-sm">
|
||||
<option value="">— Workflow —</option>
|
||||
{% for key, label in workflow_types.items() %}<option value="{{ key }}" {{ 'selected' if request.args.get('workflow_type') == key }}>{{ label }}</option>{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-6 col-md-2">
|
||||
<select name="type" class="form-select form-select-sm">
|
||||
<option value="">— Type —</option>
|
||||
|
|
@ -107,6 +113,7 @@
|
|||
<div class="d-flex align-items-center gap-1 mb-1 flex-wrap" style="font-size:0.8rem;">
|
||||
<span class="badge bg-{{ interv.status_color }}">{{ interv.status_label }}</span>
|
||||
<span class="badge bg-{{ interv.priority_color }}">{{ interv.priority_label }}</span>
|
||||
<span class="badge bg-secondary">{{ interv.workflow_label }}</span>
|
||||
{% if interv.type == 'curatif' %}
|
||||
<span class="badge bg-danger">Curatif</span>
|
||||
{% else %}
|
||||
|
|
@ -124,7 +131,7 @@
|
|||
</div>
|
||||
<div class="fw-bold text-dark">#{{ interv.id }} — {{ interv.title[:80] }}{% if interv.title|length > 80 %}…{% endif %}</div>
|
||||
<div class="text-muted" style="font-size:0.8rem;">
|
||||
<i class="bi bi-hdd"></i> {{ interv.equipment.name[:40] }}{% if interv.equipment.name|length > 40 %}…{% endif %}
|
||||
<i class="bi bi-hdd"></i> {{ interv.equipment.name[:40] if interv.equipment else (interv.room.name if interv.room else 'Localisation générale') }}{% if interv.equipment and interv.equipment.name|length > 40 %}…{% endif %}
|
||||
{% if interv.assigned_to %}
|
||||
· <i class="bi bi-person-badge"></i> {{ interv.assigned_to.full_name }}
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -71,6 +71,15 @@
|
|||
<small class="text-success"><i class="bi bi-lightbulb"></i> Suggestion: {{ json_suggestions.type }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Parcours de workflow</label>
|
||||
<select name="workflow_type" class="form-select">
|
||||
{% for key, label in workflow_types.items() %}
|
||||
<option value="{{ key }}" {% if (intervention.workflow_type if intervention is defined else (prefilled.workflow_type if prefilled is defined else 'corrective')) == key %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<small class="text-muted">Le parcours détermine les étapes affichées dans la fiche.</small>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Lot</label>
|
||||
<select name="lot_id" class="form-select">
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ def create_interventions_for_day(date):
|
|||
estimated_duration=task.estimated_duration or 30,
|
||||
status='en_attente',
|
||||
type='preventif',
|
||||
workflow_type='preventive',
|
||||
assigned_to_id=task.assigned_to_id,
|
||||
company_id=task.company_id,
|
||||
author_id=current_user.id,
|
||||
|
|
|
|||
|
|
@ -63,6 +63,20 @@ 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("/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"))
|
||||
|
||||
@prevention_bp.post("/register")
|
||||
@login_required
|
||||
def add_register():
|
||||
|
|
|
|||
|
|
@ -4,6 +4,6 @@
|
|||
<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-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><form method="post" action="{{ url_for('prevention.update_action_status', id=x.id) }}" class="d-flex gap-1"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><select name="status" class="form-select form-select-sm"><option value="a_faire" {% if x.status == 'a_faire' %}selected{% endif %}>À faire</option><option value="validee" {% if x.status == 'validee' %}selected{% endif %}>Validée</option><option value="planifiee" {% if x.status == 'planifiee' %}selected{% endif %}>Planifiée</option><option value="en_cours" {% if x.status == 'en_cours' %}selected{% endif %}>En cours</option><option value="verifiee" {% if x.status == 'verifiee' %}selected{% endif %}>Vérifiée</option><option value="cloturee" {% if x.status == 'cloturee' %}selected{% endif %}>Clôturée</option><option value="refusee" {% if x.status == 'refusee' %}selected{% endif %}>Refusée</option></select><button class="btn btn-sm btn-outline-secondary">OK</button></form></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 %}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
"""Ajoute l'origine fonctionnelle des interventions."""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "ff6a7b8c9d0e"
|
||||
down_revision = "fe5f6a7b8c9d"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
def upgrade():
|
||||
op.add_column("interventions", sa.Column("workflow_type", sa.String(20), nullable=False, server_default="corrective"))
|
||||
op.create_index("ix_interventions_workflow_type", "interventions", ["workflow_type"])
|
||||
|
||||
def downgrade():
|
||||
op.drop_index("ix_interventions_workflow_type", table_name="interventions")
|
||||
op.drop_column("interventions", "workflow_type")
|
||||
Loading…
Reference in a new issue