Ajouter le refus motivé des interventions
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
16c5a5ff88
commit
fe7deff504
5 changed files with 76 additions and 6 deletions
|
|
@ -15,6 +15,7 @@ INTERVENTION_STATUSES = {
|
|||
"terminee": {"label": "Terminée", "color": "success", "icon": "check-circle"},
|
||||
"cloturee": {"label": "Clôturée", "color": "dark", "icon": "archive"},
|
||||
"annulee": {"label": "Annulée", "color": "danger", "icon": "times-circle"},
|
||||
"refusee": {"label": "Refusée", "color": "danger", "icon": "ban"},
|
||||
"reportee": {"label": "Reportée", "color": "info", "icon": "calendar-alt"},
|
||||
"ignoree": {"label": "Ignorée", "color": "secondary", "icon": "dash-circle"},
|
||||
# Étapes détaillées d'une demande de travaux (dans la fiche intervention)
|
||||
|
|
@ -31,13 +32,13 @@ INTERVENTION_STATUSES = {
|
|||
}
|
||||
|
||||
INTERVENTION_TRANSITIONS = {
|
||||
"brouillon": {"en_attente", "planifiee", "attente_chef", "demande_departement", "attente_devis", "en_cours", "annulee"},
|
||||
"en_attente": {"planifiee", "en_cours", "annulee"},
|
||||
"planifiee": {"en_cours", "reportee", "annulee", "ignoree"},
|
||||
"brouillon": {"en_attente", "planifiee", "attente_chef", "demande_departement", "attente_devis", "en_cours", "annulee", "refusee"},
|
||||
"en_attente": {"planifiee", "en_cours", "annulee", "refusee"},
|
||||
"planifiee": {"en_cours", "reportee", "annulee", "ignoree", "refusee"},
|
||||
"reportee": {"planifiee", "annulee"},
|
||||
"en_cours": {"en_attente_pieces", "en_attente_entreprise", "terminee", "annulee"},
|
||||
"en_attente_pieces": {"en_cours", "annulee"},
|
||||
"en_attente_entreprise": {"en_cours", "annulee"},
|
||||
"en_cours": {"en_attente_pieces", "en_attente_entreprise", "terminee", "annulee", "refusee"},
|
||||
"en_attente_pieces": {"en_cours", "annulee", "refusee"},
|
||||
"en_attente_entreprise": {"en_cours", "annulee", "refusee"},
|
||||
"terminee": {"cloturee", "en_cours"},
|
||||
"cloturee": set(),
|
||||
"annulee": {"brouillon"},
|
||||
|
|
@ -52,6 +53,7 @@ INTERVENTION_TRANSITIONS = {
|
|||
"travaux_en_cours": {"travaux_termines", "en_attente_pieces", "annulee"},
|
||||
"travaux_termines": {"reception", "cloturee"},
|
||||
"reception": {"cloturee", "travaux_en_cours"},
|
||||
"refusee": {"brouillon"},
|
||||
}
|
||||
|
||||
# Statuts équipements
|
||||
|
|
|
|||
|
|
@ -93,6 +93,9 @@ class Intervention(db.Model):
|
|||
is_deleted = db.Column(db.Boolean, default=False)
|
||||
deleted_at = db.Column(db.DateTime, nullable=True)
|
||||
deleted_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
|
||||
refusal_reason = db.Column(db.Text, nullable=True)
|
||||
refused_at = db.Column(db.DateTime, nullable=True)
|
||||
refused_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=True)
|
||||
|
||||
# ENT
|
||||
ent_message_id = db.Column(db.String(100), nullable=True)
|
||||
|
|
@ -137,6 +140,7 @@ class Intervention(db.Model):
|
|||
technician = db.relationship("User", foreign_keys=[technician_id])
|
||||
author = db.relationship("User", foreign_keys=[author_id])
|
||||
deleted_by = db.relationship("User", foreign_keys=[deleted_by_id])
|
||||
refused_by = db.relationship("User", foreign_keys=[refused_by_id])
|
||||
|
||||
# Lot relation
|
||||
lot = db.relationship("Lot", backref="interventions_list")
|
||||
|
|
@ -160,6 +164,7 @@ class Intervention(db.Model):
|
|||
'cloturee': 'dark',
|
||||
'annulee': 'danger',
|
||||
'ignoree': 'secondary',
|
||||
'refusee': 'danger',
|
||||
'attente_chef': 'warning', 'demande_departement': 'info',
|
||||
'attente_devis': 'orange', 'devis_a_valider': 'warning',
|
||||
'devis_accepte': 'success', 'entreprise_mandatee': 'info',
|
||||
|
|
@ -182,6 +187,7 @@ class Intervention(db.Model):
|
|||
'cloturee': 'Cloturee',
|
||||
'annulee': 'Annulee',
|
||||
'ignoree': 'Ignoree',
|
||||
'refusee': 'Refusée',
|
||||
'attente_chef': 'Validation du chef', 'demande_departement': 'Demande au Département',
|
||||
'attente_devis': 'Devis attendu', 'devis_a_valider': 'Devis à valider',
|
||||
'devis_accepte': 'Devis accepté', 'entreprise_mandatee': 'Entreprise mandatée',
|
||||
|
|
|
|||
|
|
@ -549,6 +549,29 @@ def change_status(id):
|
|||
return redirect(url_for('interventions.detail', id=intervention.id))
|
||||
|
||||
|
||||
@interventions_bp.route('/<int:id>/refuse', methods=['POST'])
|
||||
@login_required
|
||||
def refuse(id):
|
||||
"""Refuse une demande avec un motif obligatoire."""
|
||||
intervention = Intervention.query.get_or_404(id)
|
||||
reason = (request.form.get('refusal_reason') or '').strip()
|
||||
if not reason:
|
||||
flash('Le motif du refus est obligatoire.', 'danger')
|
||||
return redirect(url_for('interventions.detail', id=id))
|
||||
if intervention.status in {'terminee', 'cloturee', 'refusee'}:
|
||||
flash('Cette intervention ne peut plus être refusée depuis son statut actuel.', 'danger')
|
||||
return redirect(url_for('interventions.detail', id=id))
|
||||
old_status = intervention.status
|
||||
intervention.status = 'refusee'
|
||||
intervention.refusal_reason = reason
|
||||
intervention.refused_at = datetime.now(timezone.utc)
|
||||
intervention.refused_by_id = current_user.id
|
||||
db.session.add(StatusChange(intervention_id=id, from_status=old_status, to_status='refusee', changed_by_id=current_user.id, comment=reason))
|
||||
db.session.commit()
|
||||
flash('Intervention refusée et motif enregistré.', 'success')
|
||||
return redirect(url_for('interventions.detail', id=id))
|
||||
|
||||
|
||||
@interventions_bp.route('/<int:id>/comment', methods=['POST'])
|
||||
@login_required
|
||||
def add_comment(id):
|
||||
|
|
|
|||
|
|
@ -102,6 +102,17 @@
|
|||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
{% if intervention.status not in ['terminee', 'cloturee', 'refusee', 'annulee'] %}
|
||||
<hr class="my-3">
|
||||
<form method="POST" action="{{ url_for('interventions.refuse', id=intervention.id) }}" onsubmit="return confirm('Refuser cette intervention ? Le motif sera conservé dans l\'historique.')">
|
||||
<label class="form-label small text-muted">Refuser la demande</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<input type="text" name="refusal_reason" class="form-control" required minlength="3" placeholder="Motif obligatoire (ex. demande non pertinente)...">
|
||||
<button type="submit" class="btn btn-outline-danger"><i class="bi bi-ban"></i> Refuser</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if intervention.equipment and intervention.equipment.status not in ['a_jeter', 'jete'] %}
|
||||
<hr class="my-3">
|
||||
|
|
@ -116,6 +127,14 @@
|
|||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
{% if intervention.status == 'refusee' %}
|
||||
<div class="alert alert-danger mb-3">
|
||||
<h6 class="alert-heading"><i class="bi bi-ban"></i> Intervention refusée</h6>
|
||||
<div>{{ intervention.refusal_reason or 'Motif non renseigné' }}</div>
|
||||
<small>Le {{ intervention.refused_at|datetime_fmt if intervention.refused_at else '—' }}{% if intervention.refused_by %} par {{ intervention.refused_by.full_name }}{% endif %}</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
|
|
|
|||
20
migrations/versions/fe5f6a7b8c9d_add_intervention_refusal.py
Normal file
20
migrations/versions/fe5f6a7b8c9d_add_intervention_refusal.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
"""Ajoute le refus motivé des interventions."""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision = "fe5f6a7b8c9d"
|
||||
down_revision = "fd4e5f6a7b8c"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column("interventions", sa.Column("refusal_reason", sa.Text(), nullable=True))
|
||||
op.add_column("interventions", sa.Column("refused_at", sa.DateTime(), nullable=True))
|
||||
op.add_column("interventions", sa.Column("refused_by_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column("interventions", "refused_by_id")
|
||||
op.drop_column("interventions", "refused_at")
|
||||
op.drop_column("interventions", "refusal_reason")
|
||||
Loading…
Reference in a new issue