621 lines
28 KiB
Python
621 lines
28 KiB
Python
"""Interventions Crud Routes - GMAO Collège"""
|
||
from flask import Blueprint, render_template, redirect, url_for, request, flash, jsonify
|
||
from flask_login import login_required, current_user
|
||
from app_new.extensions import db
|
||
from ..core.models.maintenance import Intervention, InterventionComment
|
||
from ..core.models.equipment import Equipment
|
||
from ..core.models.user import User
|
||
from datetime import datetime
|
||
|
||
interventions_bp = Blueprint("interventions", __name__, url_prefix="/interventions", template_folder="templates")
|
||
|
||
"""
|
||
Interventions Routes - GMAO Collège
|
||
Gestion des interventions de maintenance
|
||
"""
|
||
from flask import Blueprint, render_template, redirect, url_for, request, flash
|
||
from flask_login import login_required, current_user
|
||
from datetime import datetime, timezone
|
||
from app_new.extensions import db
|
||
from ..core.models.maintenance import Intervention, StatusChange, InterventionComment, Lot
|
||
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
|
||
|
||
interventions_bp = Blueprint('interventions', __name__)
|
||
|
||
|
||
@interventions_bp.route('/')
|
||
@login_required
|
||
def index():
|
||
"""Liste des interventions avec filtres avances et tri par colonne."""
|
||
page = request.args.get('page', 1, type=int)
|
||
status = request.args.get('status', '')
|
||
priority = request.args.get('priority', '')
|
||
intervention_type = request.args.get('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', '')
|
||
date_from = request.args.get('date_from', '')
|
||
date_to = request.args.get('date_to', '')
|
||
sort_col = request.args.get('sort', 'created_at')
|
||
sort_dir = request.args.get('dir', 'desc')
|
||
show_trashed = request.args.get('trashed', '0') == '1'
|
||
|
||
query = Intervention.query
|
||
|
||
if show_trashed:
|
||
query = query.filter_by(is_deleted=True)
|
||
else:
|
||
query = query.filter_by(is_deleted=False)
|
||
|
||
if status:
|
||
query = query.filter_by(status=status)
|
||
if priority:
|
||
query = query.filter_by(priority=priority)
|
||
if intervention_type:
|
||
query = query.filter(Intervention.type == intervention_type)
|
||
if lot_id:
|
||
query = query.filter_by(lot_id=lot_id)
|
||
if assigned_to_id:
|
||
query = query.filter_by(assigned_to_id=assigned_to_id)
|
||
if date_from:
|
||
try:
|
||
d = datetime.strptime(date_from, '%Y-%m-%d').date()
|
||
query = query.filter(Intervention.scheduled_date >= d)
|
||
except ValueError:
|
||
pass
|
||
if date_to:
|
||
try:
|
||
d = datetime.strptime(date_to, '%Y-%m-%d').date()
|
||
query = query.filter(Intervention.scheduled_date <= d)
|
||
except ValueError:
|
||
pass
|
||
if frequency:
|
||
if frequency == 'recurring':
|
||
query = query.filter(Intervention.is_recurring == True)
|
||
elif frequency == 'ponctuel':
|
||
query = query.filter(Intervention.is_recurring == False)
|
||
elif frequency.isdigit():
|
||
query = query.filter(Intervention.frequency_days == int(frequency))
|
||
|
||
# Tri par colonne (avec validation pour eviter injection)
|
||
valid_sort_cols = {'created_at', 'scheduled_date', 'priority', 'status', 'title'}
|
||
if sort_col not in valid_sort_cols:
|
||
sort_col = 'created_at'
|
||
sort_dir = 'asc' if sort_dir == 'asc' else 'desc'
|
||
order_clause = getattr(Intervention, sort_col).asc() if sort_dir == 'asc' else getattr(Intervention, sort_col).desc()
|
||
query = query.order_by(order_clause)
|
||
|
||
interventions = query.paginate(page=page, per_page=20)
|
||
lots = Lot.query.order_by(Lot.name).all()
|
||
users = User.query.filter_by(is_active=True).order_by(User.username).all()
|
||
|
||
# Compter les interventions par statut
|
||
status_counts = {}
|
||
for status_key in INTERVENTION_STATUSES.keys():
|
||
status_counts[status_key] = Intervention.query.filter_by(status=status_key, is_deleted=False).count()
|
||
|
||
# Compter les interventions à jeter
|
||
trashed_count = Intervention.query.filter_by(is_deleted=True).count()
|
||
|
||
return render_template('interventions/index.html',
|
||
interventions=interventions,
|
||
lots=lots,
|
||
users=users,
|
||
statuses=INTERVENTION_STATUSES,
|
||
priorities=PRIORITIES,
|
||
status_counts=status_counts,
|
||
trashed_count=trashed_count,
|
||
show_trashed=show_trashed,
|
||
sort_col=sort_col,
|
||
sort_dir=sort_dir)
|
||
|
||
|
||
@interventions_bp.route('/new', methods=['GET', 'POST'])
|
||
@login_required
|
||
def create():
|
||
"""Créer une intervention."""
|
||
from ..core.models.user import User
|
||
from ..outlook.models import OutlookMailInterpretation
|
||
|
||
if request.method == 'POST':
|
||
# Une intervention générale peut cibler une salle sans équipement.
|
||
equipment_value = request.form.get('equipment_id')
|
||
equipment_id = int(equipment_value) if equipment_value and equipment_value.isdigit() else None
|
||
|
||
# Déterminer le demandeur
|
||
requester_name = request.form.get('requester_name', '').strip()
|
||
if not requester_name:
|
||
# Si pas de demandeur fourni, utiliser le nom de l'utilisateur connecté
|
||
requester_name = current_user.username if hasattr(current_user, 'username') else 'Inconnu'
|
||
|
||
intervention = Intervention(
|
||
title=request.form.get('title'),
|
||
description=request.form.get('description') or '',
|
||
lot_id=request.form.get('lot_id') or None,
|
||
equipment_id=equipment_id,
|
||
room_id=request.form.get('room_id') or None,
|
||
assigned_to_id=request.form.get('assigned_to_id') or None,
|
||
author_id=current_user.id,
|
||
status=request.form.get('status', 'brouillon'),
|
||
priority=request.form.get('priority', 'normale'),
|
||
type=request.form.get('intervention_type', 'curatif'),
|
||
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,
|
||
scheduled_start=datetime.strptime(request.form.get('scheduled_start'), '%H:%M').time() if request.form.get('scheduled_start') else None,
|
||
scheduled_end=datetime.strptime(request.form.get('scheduled_end'), '%H:%M').time() if request.form.get('scheduled_end') else None
|
||
)
|
||
db.session.add(intervention)
|
||
db.session.commit()
|
||
|
||
# Marquer l'interprétation comme acceptée si fournie
|
||
from_interpretation_id = request.form.get('from_interpretation')
|
||
source = request.form.get('source', 'outlook') # 'outlook' ou 'ent'
|
||
if from_interpretation_id:
|
||
if source == 'ent':
|
||
from app_new.ent.interpretation_models import EntMessageInterpretation
|
||
from app_new.lib_ext.ent_service import move_ent_message_to_processed
|
||
interpretation = EntMessageInterpretation.query.get(from_interpretation_id)
|
||
if interpretation:
|
||
message = interpretation.message
|
||
# Déplacer le message ENT vers "Traités"
|
||
if message:
|
||
move_result = move_ent_message_to_processed(interpretation.message_id)
|
||
if move_result.get('success'):
|
||
flash('Intervention créée. Message ENT déplacé vers Traités.', 'success')
|
||
else:
|
||
flash(f'Intervention créée. Erreur déplacement ENT: {move_result.get("error")}', 'warning')
|
||
interpretation.status = 'accepted'
|
||
if message:
|
||
message.intervention_id = intervention.id
|
||
db.session.commit()
|
||
else:
|
||
from app_new.outlook.models import OutlookMail
|
||
from app_new.outlook.routes import move_mail_to_processed
|
||
interpretation = OutlookMailInterpretation.query.get(from_interpretation_id)
|
||
if interpretation:
|
||
mail = interpretation.mail
|
||
# Déplacer le mail Outlook vers "Traités"
|
||
if mail:
|
||
move_result = move_mail_to_processed(mail)
|
||
if move_result.get('success'):
|
||
flash('Intervention créée. Mail déplacé vers Traités.', 'success')
|
||
else:
|
||
flash(f'Intervention créée. Erreur déplacement mail: {move_result.get("error")}', 'warning')
|
||
interpretation.status = 'accepted'
|
||
if mail:
|
||
mail.intervention_id = intervention.id
|
||
db.session.commit()
|
||
# Flash déjà envoyé ci-dessus
|
||
else:
|
||
flash('Intervention créée avec succès.', 'success')
|
||
|
||
return redirect(url_for('interventions.detail', id=intervention.id))
|
||
|
||
# GET - Pré-remplir depuis interprétation ou tâche planifiée
|
||
from_interpretation = request.args.get('from_interpretation')
|
||
scheduled_task_id = request.args.get('scheduled_task_id', type=int)
|
||
|
||
# Valeurs par défaut
|
||
prefilled = {
|
||
'title': '',
|
||
'description': '',
|
||
'priority': 'normale',
|
||
'type': 'curatif',
|
||
'location': '',
|
||
'equipment': '',
|
||
'assignee': '',
|
||
'date': None,
|
||
'requester_name': ''
|
||
}
|
||
|
||
interpretation = None
|
||
json_suggestions = None
|
||
|
||
if from_interpretation:
|
||
source = request.args.get('source', 'outlook')
|
||
|
||
if source == 'ent':
|
||
from app_new.ent.interpretation_models import EntMessageInterpretation
|
||
interpretation = EntMessageInterpretation.query.get(from_interpretation)
|
||
else:
|
||
interpretation = OutlookMailInterpretation.query.get(from_interpretation)
|
||
|
||
if interpretation:
|
||
# Adapter selon la source
|
||
if source == 'ent':
|
||
json_suggestions = {
|
||
'title': interpretation.message.subject if interpretation.message else '',
|
||
'description': interpretation.suggested_description,
|
||
'priority': interpretation.analysis_priority,
|
||
'type': interpretation.analysis_type,
|
||
'location': '',
|
||
'equipment': interpretation.suggested_equipment,
|
||
'assignee': '',
|
||
'date': None
|
||
}
|
||
|
||
prefilled['title'] = interpretation.message.subject if interpretation.message else ''
|
||
prefilled['description'] = interpretation.suggested_description or ''
|
||
prefilled['priority'] = interpretation.analysis_priority or 'normale'
|
||
# Demandeur = sender du message ENT
|
||
prefilled['requester_name'] = interpretation.message.sender_name if interpretation.message else ''
|
||
|
||
type_map = {
|
||
'curative': 'curatif',
|
||
'preventive': 'preventif',
|
||
'administrative': 'administratif',
|
||
'formation': 'formation',
|
||
'information': 'curatif'
|
||
}
|
||
prefilled['type'] = type_map.get(interpretation.analysis_type, 'curatif')
|
||
prefilled['equipment'] = interpretation.suggested_equipment or ''
|
||
else:
|
||
json_suggestions = {
|
||
'title': interpretation.suggested_title,
|
||
'description': interpretation.suggested_description,
|
||
'priority': interpretation.suggested_urgency,
|
||
'type': interpretation.analysis_type,
|
||
'location': interpretation.suggested_location,
|
||
'equipment': interpretation.suggested_equipment,
|
||
'assignee': interpretation.suggested_assignee,
|
||
'date': interpretation.suggested_date.strftime('%d/%m/%Y') if interpretation.suggested_date else None
|
||
}
|
||
|
||
# Pré-remplir avec les valeurs de l'interprétation
|
||
prefilled['title'] = interpretation.suggested_title or ''
|
||
prefilled['description'] = interpretation.suggested_description or ''
|
||
prefilled['priority'] = interpretation.suggested_urgency or 'normale'
|
||
# Demandeur = sender du mail Outlook
|
||
prefilled['requester_name'] = interpretation.mail.from_name if interpretation.mail else ''
|
||
|
||
# Mapper le type
|
||
type_map = {
|
||
'curative': 'curatif',
|
||
'preventive': 'preventif',
|
||
'administrative': 'administratif',
|
||
'formation': 'formation',
|
||
'information': 'curatif'
|
||
}
|
||
prefilled['type'] = type_map.get(interpretation.analysis_type, 'curatif')
|
||
prefilled['location'] = interpretation.suggested_location or ''
|
||
prefilled['equipment'] = interpretation.suggested_equipment or ''
|
||
prefilled['assignee'] = interpretation.suggested_assignee or ''
|
||
prefilled['date'] = interpretation.suggested_date
|
||
|
||
# Chercher l'équipement correspondant si spécifié
|
||
equipment_id = None
|
||
equipment_match = None
|
||
if prefilled['equipment']:
|
||
eq = Equipment.query.filter(Equipment.name.ilike(f"%{prefilled['equipment']}%")).first()
|
||
if eq:
|
||
equipment_id = eq.id
|
||
equipment_match = eq
|
||
|
||
# Pré-remplir depuis tâche planifiée si fourni
|
||
if scheduled_task_id and not from_interpretation:
|
||
task = ScheduledTask.query.get(scheduled_task_id)
|
||
if task:
|
||
prefilled['title'] = f"Intervention: {task.preventive_task.name if task.preventive_task else task.lot_task.name if task.lot_task else 'Tâche planifiée'}"
|
||
prefilled['description'] = task.notes or ''
|
||
prefilled['date'] = task.scheduled_date.strftime('%Y-%m-%d') if task.scheduled_date else None
|
||
prefilled['scheduled_start'] = task.scheduled_start.strftime('%H:%M') if task.scheduled_start else None
|
||
prefilled['scheduled_end'] = task.scheduled_end.strftime('%H:%M') if task.scheduled_end else None
|
||
|
||
# Chercher la salle correspondante si spécifiée
|
||
room_id = None
|
||
room_match = None
|
||
if prefilled['location']:
|
||
room = Room.query.filter(Room.name.ilike(f"%{prefilled['location']}%")).first()
|
||
if room:
|
||
room_id = room.id
|
||
room_match = room
|
||
|
||
lots = Lot.query.order_by(Lot.name).all()
|
||
equipments = Equipment.query.filter_by(status='en_service').order_by(Equipment.name).all()
|
||
rooms = Room.query.order_by(Room.name).all()
|
||
users = User.query.filter_by(is_active=True).order_by(User.username).all()
|
||
|
||
return render_template('interventions/new.html',
|
||
lots=lots,
|
||
equipments=equipments,
|
||
rooms=rooms,
|
||
users=users,
|
||
statuses=INTERVENTION_STATUSES,
|
||
priorities=PRIORITIES,
|
||
prefilled=prefilled,
|
||
equipment_id=equipment_id,
|
||
equipment_match=equipment_match,
|
||
room_id=room_id,
|
||
room_match=room_match,
|
||
from_interpretation=from_interpretation,
|
||
interpretation=interpretation,
|
||
json_suggestions=json_suggestions,
|
||
source=source if from_interpretation else 'outlook')
|
||
|
||
|
||
@interventions_bp.route('/create-for-group', methods=['GET'])
|
||
@login_required
|
||
def create_for_group_select():
|
||
"""Sélection d'équipements pour création groupée."""
|
||
equipments = Equipment.query.filter_by(status='en_service').order_by(Equipment.name).all()
|
||
return render_template('interventions/create_for_group_select.html', equipments=equipments)
|
||
|
||
|
||
@interventions_bp.route('/create-for-group', methods=['POST'])
|
||
@login_required
|
||
def create_for_group():
|
||
"""Créer des interventions pour plusieurs équipements."""
|
||
equipment_ids = request.form.getlist('equipment_ids')
|
||
title = request.form.get('title')
|
||
description = request.form.get('description', '')
|
||
priority = request.form.get('priority', 'normale')
|
||
lot_id = request.form.get('lot_id')
|
||
|
||
if not equipment_ids:
|
||
flash('Aucun équipement sélectionné.', 'error')
|
||
return redirect(url_for('interventions.create_for_group_select'))
|
||
|
||
created_count = 0
|
||
for eq_id in equipment_ids:
|
||
equipment = Equipment.query.get(eq_id)
|
||
if equipment:
|
||
intervention = Intervention(
|
||
title=title,
|
||
description=description,
|
||
priority=priority,
|
||
lot_id=lot_id if lot_id else None,
|
||
equipment_id=equipment.id,
|
||
room_id=equipment.room_id,
|
||
status='brouillon',
|
||
intervention_type='corrective'
|
||
)
|
||
db.session.add(intervention)
|
||
created_count += 1
|
||
|
||
db.session.commit()
|
||
flash(f'{created_count} intervention(s) créée(s) avec succès.', 'success')
|
||
return redirect(url_for('interventions.index'))
|
||
|
||
|
||
@interventions_bp.route('/<int:id>')
|
||
@login_required
|
||
def detail(id):
|
||
"""Détail d'une intervention."""
|
||
intervention = Intervention.query.get_or_404(id)
|
||
transitions = [(key, value['label']) for key, value in INTERVENTION_STATUSES.items()
|
||
if key in INTERVENTION_TRANSITIONS.get(intervention.status, set())]
|
||
history = intervention.status_history.order_by(StatusChange.created_at.desc()).all()
|
||
return render_template('interventions/detail.html',
|
||
intervention=intervention,
|
||
statuses=INTERVENTION_STATUSES,
|
||
transitions=transitions,
|
||
history=history,
|
||
services=Service.query.filter_by(is_active=True).order_by(Service.name).all(),
|
||
companies=Company.query.filter_by(is_active=True).order_by(Company.name).all())
|
||
|
||
|
||
@interventions_bp.route('/<int:id>/work-details', methods=['POST'])
|
||
@login_required
|
||
def save_work_details(id):
|
||
"""Enregistre les informations de travaux depuis la fiche intervention."""
|
||
intervention = Intervention.query.get_or_404(id)
|
||
intervention.execution_mode = request.form.get('execution_mode', 'interne')
|
||
service_id = request.form.get('department_service_id')
|
||
intervention.department_service_id = int(service_id) if service_id and service_id.isdigit() else None
|
||
company_id = request.form.get('company_id')
|
||
intervention.company_id = int(company_id) if company_id and company_id.isdigit() else None
|
||
intervention.quote_status = request.form.get('quote_status', 'non_requis')
|
||
intervention.quote_reference = request.form.get('quote_reference') or None
|
||
intervention.quote_amount_ht = float(request.form['quote_amount_ht']) if request.form.get('quote_amount_ht') else None
|
||
intervention.quote_amount_ttc = float(request.form['quote_amount_ttc']) if request.form.get('quote_amount_ttc') else None
|
||
for field in ('department_request_date', 'chief_validation_date', 'department_validation_date', 'quote_date', 'quote_valid_until'):
|
||
value = request.form.get(field)
|
||
setattr(intervention, field, datetime.strptime(value, '%Y-%m-%d').date() if value else None)
|
||
intervention.work_notes = request.form.get('work_notes') or None
|
||
db.session.commit()
|
||
flash('Workflow travaux enregistré.', 'success')
|
||
return redirect(url_for('interventions.detail', id=id))
|
||
|
||
|
||
@interventions_bp.route('/<int:id>/edit', methods=['GET', 'POST'])
|
||
@login_required
|
||
def edit(id):
|
||
"""Modifier une intervention."""
|
||
from ..core.models.user import User
|
||
|
||
intervention = Intervention.query.get_or_404(id)
|
||
|
||
if request.method == 'POST':
|
||
old_status = intervention.status
|
||
|
||
intervention.title = request.form.get('title')
|
||
intervention.description = request.form.get('description')
|
||
intervention.lot_id = request.form.get('lot_id') or None
|
||
intervention.equipment_id = request.form.get('equipment_id') or None
|
||
intervention.room_id = request.form.get('room_id') or None
|
||
intervention.assigned_to_id = request.form.get('assigned_to_id') or None
|
||
# Ne pas changer le statut si le formulaire ne le contient pas
|
||
new_status = request.form.get('status')
|
||
if new_status:
|
||
intervention.status = new_status
|
||
intervention.priority = request.form.get('priority')
|
||
intervention.type = request.form.get('intervention_type', intervention.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
|
||
|
||
# Enregistrer le changement de statut seulement si le statut a changé
|
||
if new_status and old_status != intervention.status:
|
||
status_change = StatusChange(
|
||
intervention_id=intervention.id,
|
||
from_status=old_status,
|
||
to_status=intervention.status,
|
||
changed_by_id=current_user.id
|
||
)
|
||
db.session.add(status_change)
|
||
|
||
db.session.commit()
|
||
flash('Intervention modifiée avec succès.', 'success')
|
||
return redirect(url_for('interventions.detail', id=intervention.id))
|
||
|
||
lots = Lot.query.order_by(Lot.name).all()
|
||
equipments = Equipment.query.filter_by(status='en_service').order_by(Equipment.name).all()
|
||
rooms = Room.query.order_by(Room.name).all()
|
||
users = User.query.filter_by(is_active=True).order_by(User.username).all()
|
||
|
||
# Pré-remplir avec les valeurs existantes pour l'édition
|
||
prefilled = {
|
||
'title': intervention.title,
|
||
'description': intervention.description or '',
|
||
'priority': intervention.priority,
|
||
'type': intervention.type,
|
||
'location': intervention.equipment.room.name if intervention.equipment and intervention.equipment.room else '',
|
||
'equipment': intervention.equipment.name if intervention.equipment else '',
|
||
'assignee': intervention.assigned_to.username if intervention.assigned_to else '',
|
||
'date': intervention.scheduled_date,
|
||
'requester_name': intervention.requester_name or ''
|
||
}
|
||
|
||
return render_template('interventions/new.html',
|
||
intervention=intervention,
|
||
lots=lots,
|
||
equipments=equipments,
|
||
rooms=rooms,
|
||
users=users,
|
||
statuses=INTERVENTION_STATUSES,
|
||
priorities=PRIORITIES,
|
||
prefilled=prefilled,
|
||
title="Modifier l'intervention")
|
||
|
||
|
||
@interventions_bp.route('/<int:id>/status', methods=['POST'])
|
||
@login_required
|
||
def change_status(id):
|
||
"""Changer le statut d'une intervention."""
|
||
intervention = Intervention.query.get_or_404(id)
|
||
old_status = intervention.status
|
||
new_status = request.form.get('status')
|
||
comment = request.form.get('comment', '')
|
||
|
||
allowed = INTERVENTION_TRANSITIONS.get(old_status, set())
|
||
if new_status != old_status and new_status not in allowed:
|
||
flash(f'Transition interdite : {old_status} → {new_status}.', 'danger')
|
||
return redirect(url_for('interventions.detail', id=intervention.id))
|
||
|
||
if new_status == 'en_cours':
|
||
from ..core.services.planning_service import PlanningService
|
||
reason = PlanningService.maintenance_unavailability(
|
||
datetime.now(timezone.utc).date(), user_id=current_user.id
|
||
)
|
||
if reason:
|
||
flash(f'Impossible de démarrer une maintenance aujourd’hui : {reason}.', 'danger')
|
||
return redirect(url_for('interventions.detail', id=intervention.id))
|
||
|
||
if new_status != old_status:
|
||
intervention.status = new_status
|
||
|
||
status_change = StatusChange(
|
||
intervention_id=intervention.id,
|
||
from_status=old_status,
|
||
to_status=new_status,
|
||
changed_by_id=current_user.id,
|
||
comment=comment
|
||
)
|
||
db.session.add(status_change)
|
||
|
||
if new_status == 'terminee':
|
||
now = datetime.now(timezone.utc)
|
||
intervention.completed_date = now.date()
|
||
intervention.completed_at = now
|
||
|
||
db.session.commit()
|
||
flash('Statut modifié.', 'success')
|
||
|
||
return redirect(url_for('interventions.detail', id=intervention.id))
|
||
|
||
|
||
@interventions_bp.route('/<int:id>/comment', methods=['POST'])
|
||
@login_required
|
||
def add_comment(id):
|
||
"""Ajouter un commentaire à une intervention."""
|
||
intervention = Intervention.query.get_or_404(id)
|
||
content = request.form.get('content')
|
||
|
||
if content:
|
||
comment = InterventionComment(
|
||
intervention_id=intervention.id,
|
||
author_id=current_user.id,
|
||
content=content
|
||
)
|
||
db.session.add(comment)
|
||
db.session.commit()
|
||
flash('Commentaire ajouté.', 'success')
|
||
|
||
return redirect(url_for('interventions.detail', id=intervention.id))
|
||
|
||
|
||
@interventions_bp.route('/<int:id>/soft-delete', methods=['POST'])
|
||
@login_required
|
||
def soft_delete(id):
|
||
"""Mettre une intervention en corbeille (soft delete)."""
|
||
intervention = Intervention.query.get_or_404(id)
|
||
intervention.is_deleted = True
|
||
intervention.deleted_at = datetime.now(timezone.utc)
|
||
intervention.deleted_by_id = current_user.id
|
||
db.session.commit()
|
||
flash('Intervention mise en corbeille.', 'success')
|
||
return redirect(url_for('interventions.index'))
|
||
|
||
|
||
@interventions_bp.route('/<int:id>/postpone', methods=['POST'])
|
||
@login_required
|
||
def postpone(id):
|
||
"""Reporter une intervention : motif, compteur, conservation date originale."""
|
||
intervention = Intervention.query.get_or_404(id)
|
||
new_date_str = request.form.get('new_scheduled_date')
|
||
reason = request.form.get('reason', '').strip()
|
||
|
||
if not new_date_str:
|
||
flash('Nouvelle date requise.', 'danger')
|
||
return redirect(url_for('interventions.detail', id=id))
|
||
|
||
try:
|
||
new_date = datetime.strptime(new_date_str, '%Y-%m-%d').date()
|
||
except ValueError:
|
||
flash('Format de date invalide.', 'danger')
|
||
return redirect(url_for('interventions.detail', id=id))
|
||
|
||
# Conserver la date originale à la première reprogrammation
|
||
if intervention.postpone_count == 0 and intervention.scheduled_date:
|
||
intervention.original_scheduled_date = intervention.scheduled_date
|
||
|
||
intervention.scheduled_date = new_date
|
||
intervention.postpone_count += 1
|
||
intervention.last_postpone_reason = reason
|
||
intervention.last_postpone_at = datetime.now(timezone.utc)
|
||
|
||
# Commentaire d'historique
|
||
comment_text = f"Report #{intervention.postpone_count}"
|
||
if intervention.original_scheduled_date:
|
||
comment_text += f" (date originale {intervention.original_scheduled_date.strftime('%d/%m/%Y')})"
|
||
comment_text += f" → {new_date.strftime('%d/%m/%Y')}"
|
||
if reason:
|
||
comment_text += f" — {reason}"
|
||
|
||
status_change = StatusChange(
|
||
intervention_id=intervention.id,
|
||
from_status=intervention.status,
|
||
to_status='reportee',
|
||
changed_by_id=current_user.id,
|
||
comment=comment_text
|
||
)
|
||
db.session.add(status_change)
|
||
db.session.commit()
|
||
|
||
flash(f"Intervention reportée au {new_date.strftime('%d/%m/%Y')}.", 'success')
|
||
return redirect(url_for('interventions.detail', id=id))
|