diff --git a/app_new/constants.py b/app_new/constants.py index d9b96ac..767fe43 100644 --- a/app_new/constants.py +++ b/app_new/constants.py @@ -17,6 +17,17 @@ INTERVENTION_STATUSES = { "annulee": {"label": "Annulée", "color": "danger", "icon": "times-circle"}, "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) + "attente_chef": {"label": "Validation du chef", "color": "warning", "icon": "person-check"}, + "demande_departement": {"label": "Demande au Département", "color": "info", "icon": "building"}, + "attente_devis": {"label": "Devis attendu", "color": "orange", "icon": "file-earmark-text"}, + "devis_a_valider": {"label": "Devis à valider", "color": "warning", "icon": "file-check"}, + "devis_accepte": {"label": "Devis accepté", "color": "success", "icon": "check2-circle"}, + "entreprise_mandatee": {"label": "Entreprise mandatée", "color": "info", "icon": "truck"}, + "travaux_planifies": {"label": "Travaux planifiés", "color": "primary", "icon": "calendar-check"}, + "travaux_en_cours": {"label": "Travaux en cours", "color": "primary", "icon": "tools"}, + "travaux_termines": {"label": "Travaux terminés", "color": "success", "icon": "check-circle"}, + "reception": {"label": "Réception", "color": "success", "icon": "clipboard-check"}, } INTERVENTION_TRANSITIONS = { @@ -31,6 +42,16 @@ INTERVENTION_TRANSITIONS = { "cloturee": set(), "annulee": {"brouillon"}, "ignoree": {"planifiee"}, + "attente_chef": {"en_cours", "demande_departement", "attente_devis", "annulee"}, + "demande_departement": {"attente_devis", "devis_a_valider", "annulee"}, + "attente_devis": {"devis_a_valider", "travaux_en_cours", "annulee"}, + "devis_a_valider": {"devis_accepte", "attente_devis", "annulee"}, + "devis_accepte": {"entreprise_mandatee", "travaux_planifies", "annulee"}, + "entreprise_mandatee": {"travaux_planifies", "travaux_en_cours", "annulee"}, + "travaux_planifies": {"travaux_en_cours", "reportee", "annulee"}, + "travaux_en_cours": {"travaux_termines", "en_attente_pieces", "annulee"}, + "travaux_termines": {"reception", "cloturee"}, + "reception": {"cloturee", "travaux_en_cours"}, } # Statuts équipements diff --git a/app_new/core/models/maintenance.py b/app_new/core/models/maintenance.py index eacb80e..a0c1173 100644 --- a/app_new/core/models/maintenance.py +++ b/app_new/core/models/maintenance.py @@ -42,6 +42,20 @@ class Intervention(db.Model): # Entreprise extérieure company_id = db.Column(db.Integer, db.ForeignKey("companies.id"), nullable=True) + + # Workflow travaux intégré à l'intervention + execution_mode = db.Column(db.String(30), nullable=False, default="interne") + department_service_id = db.Column(db.Integer, db.ForeignKey("services.id"), nullable=True) + department_request_date = db.Column(db.Date, nullable=True) + chief_validation_date = db.Column(db.Date, nullable=True) + department_validation_date = db.Column(db.Date, nullable=True) + quote_status = db.Column(db.String(30), nullable=False, default="non_requis") + quote_reference = db.Column(db.String(100), nullable=True) + quote_amount_ht = db.Column(db.Float, nullable=True) + quote_amount_ttc = db.Column(db.Float, nullable=True) + quote_date = db.Column(db.Date, nullable=True) + quote_valid_until = db.Column(db.Date, nullable=True) + work_notes = db.Column(db.Text, nullable=True) # Équipement equipment_id = db.Column(db.Integer, db.ForeignKey("equipments.id"), nullable=True) @@ -129,6 +143,8 @@ class Intervention(db.Model): room = db.relationship("Room", foreign_keys=[room_id]) preventive_task = db.relationship("PreventiveTask", foreign_keys=[preventive_task_id]) lot_task = db.relationship("LotTask", foreign_keys=[lot_task_id]) + company = db.relationship("Company", foreign_keys=[company_id]) + department_service = db.relationship("Service", foreign_keys=[department_service_id]) @property def status_color(self): @@ -144,6 +160,11 @@ class Intervention(db.Model): 'cloturee': 'dark', 'annulee': 'danger', 'ignoree': 'secondary', + 'attente_chef': 'warning', 'demande_departement': 'info', + 'attente_devis': 'orange', 'devis_a_valider': 'warning', + 'devis_accepte': 'success', 'entreprise_mandatee': 'info', + 'travaux_planifies': 'primary', 'travaux_en_cours': 'primary', + 'travaux_termines': 'success', 'reception': 'success', } return colors.get(self.status, 'secondary') @@ -161,6 +182,11 @@ class Intervention(db.Model): 'cloturee': 'Cloturee', 'annulee': 'Annulee', 'ignoree': 'Ignoree', + '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', + 'travaux_planifies': 'Travaux planifiés', 'travaux_en_cours': 'Travaux en cours', + 'travaux_termines': 'Travaux terminés', 'reception': 'Réception', } return labels.get(self.status, self.status) diff --git a/app_new/interventions/crud.py b/app_new/interventions/crud.py index cf89c7b..d64a2f5 100644 --- a/app_new/interventions/crud.py +++ b/app_new/interventions/crud.py @@ -21,6 +21,7 @@ from ..core.models.maintenance import Intervention, StatusChange, InterventionCo 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__) @@ -386,9 +387,39 @@ def create_for_group(): 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) + 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('//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('//edit', methods=['GET', 'POST']) diff --git a/app_new/interventions/templates/detail.html b/app_new/interventions/templates/detail.html index e4d336a..bc2f2b5 100644 --- a/app_new/interventions/templates/detail.html +++ b/app_new/interventions/templates/detail.html @@ -52,7 +52,7 @@
Workflow
- {% set workflow_order = ['brouillon', 'attente_chef', 'valide_chef', 'demande_gima', 'gima_accepte', 'budget_previsionnel', 'en_cours', 'en_attente_pieces', 'en_attente_entreprise', 'terminee', 'cloturee'] %} + {% 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'] %} {% for step in workflow_order %} {% if step in statuses %} {% set is_current = (step == intervention.status) %} @@ -202,6 +202,51 @@
{% endif %} + +
+
+
Suivi des travaux
+ Dans cette intervention +
+
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+
+ +
+
+
+
diff --git a/migrations/versions/fd4e5f6a7b8c_integrer_workflow_travaux_interventions.py b/migrations/versions/fd4e5f6a7b8c_integrer_workflow_travaux_interventions.py new file mode 100644 index 0000000..1780bad --- /dev/null +++ b/migrations/versions/fd4e5f6a7b8c_integrer_workflow_travaux_interventions.py @@ -0,0 +1,28 @@ +"""Intègre le workflow des travaux aux interventions.""" +from alembic import op +import sqlalchemy as sa + +revision = "fd4e5f6a7b8c" +down_revision = "fc3d4e5f6a7b" +branch_labels = None +depends_on = None + + +def upgrade(): + op.add_column("interventions", sa.Column("execution_mode", sa.String(30), nullable=False, server_default="interne")) + op.add_column("interventions", sa.Column("department_service_id", sa.Integer(), sa.ForeignKey("services.id"), nullable=True)) + op.add_column("interventions", sa.Column("department_request_date", sa.Date(), nullable=True)) + op.add_column("interventions", sa.Column("chief_validation_date", sa.Date(), nullable=True)) + op.add_column("interventions", sa.Column("department_validation_date", sa.Date(), nullable=True)) + op.add_column("interventions", sa.Column("quote_status", sa.String(30), nullable=False, server_default="non_requis")) + op.add_column("interventions", sa.Column("quote_reference", sa.String(100), nullable=True)) + op.add_column("interventions", sa.Column("quote_amount_ht", sa.Float(), nullable=True)) + op.add_column("interventions", sa.Column("quote_amount_ttc", sa.Float(), nullable=True)) + op.add_column("interventions", sa.Column("quote_date", sa.Date(), nullable=True)) + op.add_column("interventions", sa.Column("quote_valid_until", sa.Date(), nullable=True)) + op.add_column("interventions", sa.Column("work_notes", sa.Text(), nullable=True)) + + +def downgrade(): + for name in ("work_notes", "quote_valid_until", "quote_date", "quote_amount_ttc", "quote_amount_ht", "quote_reference", "quote_status", "department_validation_date", "chief_validation_date", "department_request_date", "department_service_id", "execution_mode"): + op.drop_column("interventions", name)