Lier les tâches préventives aux contrats
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run

This commit is contained in:
root 2026-08-20 22:28:47 +00:00
parent 2d6e9fb0ca
commit d18deec5d5
7 changed files with 29 additions and 3 deletions

View file

@ -375,6 +375,8 @@ class LotTask(db.Model):
tache = db.Column(db.Text, nullable=True) tache = db.Column(db.Text, nullable=True)
periodicite = db.Column(db.String(200), nullable=True) periodicite = db.Column(db.String(200), nullable=True)
contrat = db.Column(db.Boolean, default=False) contrat = db.Column(db.Boolean, default=False)
company_id = db.Column(db.Integer, db.ForeignKey("companies.id"), nullable=True)
contract_id = db.Column(db.Integer, db.ForeignKey("contracts.id"), nullable=True)
gestionnaire = db.Column(db.String(50), nullable=True) gestionnaire = db.Column(db.String(50), nullable=True)
jours_entre_interventions = db.Column(db.Integer, nullable=True) jours_entre_interventions = db.Column(db.Integer, nullable=True)
type_maintenance = db.Column(db.String(50), nullable=True) type_maintenance = db.Column(db.String(50), nullable=True)
@ -390,6 +392,8 @@ class LotTask(db.Model):
unavailable_policy = db.Column(db.String(20), nullable=False, default="suspend") unavailable_policy = db.Column(db.String(20), nullable=False, default="suspend")
after_repair_policy = db.Column(db.String(20), nullable=False, default="preserve") after_repair_policy = db.Column(db.String(20), nullable=False, default="preserve")
is_safety_critical = db.Column(db.Boolean, nullable=False, default=False) is_safety_critical = db.Column(db.Boolean, nullable=False, default=False)
company = db.relationship("Company")
contract = db.relationship("Contract")
def effective_duration(self): def effective_duration(self):
"""Duree previsionnelle auto-adaptee pour cette tache de lot. """Duree previsionnelle auto-adaptee pour cette tache de lot.

View file

@ -426,6 +426,7 @@ class PreventiveTask(db.Model):
has_contract = db.Column(db.Boolean, default=False) has_contract = db.Column(db.Boolean, default=False)
manager = db.Column(db.String(50)) manager = db.Column(db.String(50))
company_id = db.Column(db.Integer, db.ForeignKey("companies.id")) company_id = db.Column(db.Integer, db.ForeignKey("companies.id"))
contract_id = db.Column(db.Integer, db.ForeignKey("contracts.id"), nullable=True)
trigger_type = db.Column(db.String(20), default="time") trigger_type = db.Column(db.String(20), default="time")
meter_type_required = db.Column(db.String(50)) meter_type_required = db.Column(db.String(50))
@ -441,6 +442,7 @@ class PreventiveTask(db.Model):
lot = db.relationship("Lot", backref=db.backref("preventive_tasks", lazy="dynamic")) lot = db.relationship("Lot", backref=db.backref("preventive_tasks", lazy="dynamic"))
category = db.relationship("EquipmentCategory", backref=db.backref("preventive_tasks", lazy="dynamic")) category = db.relationship("EquipmentCategory", backref=db.backref("preventive_tasks", lazy="dynamic"))
company = db.relationship("Company") company = db.relationship("Company")
contract = db.relationship("Contract")
consumables_needed = db.relationship("PreventiveTaskConsumable", back_populates="task", lazy="dynamic") consumables_needed = db.relationship("PreventiveTaskConsumable", back_populates="task", lazy="dynamic")
def __repr__(self): def __repr__(self):

View file

@ -137,6 +137,8 @@ def generate_due_tasks(task: LotTask, event=None, today=None, equipment_ids=None
status = "suspended" if unavailable and not task.is_safety_critical and task.unavailable_policy == "suspend" else "planned" status = "suspended" if unavailable and not task.is_safety_critical and task.unavailable_policy == "suspend" else "planned"
item = ScheduledTask(lot_task_id=task.id, equipment_id=equipment.id, item = ScheduledTask(lot_task_id=task.id, equipment_id=equipment.id,
room_id=room.id if room else None, room_id=room.id if room else None,
company_id=task.company_id,
contract_id=task.contract_id,
scheduled_date=due, estimated_duration=task.effective_duration(), status=status, scheduled_date=due, estimated_duration=task.effective_duration(), status=status,
suspension_reason="Équipement indisponible" if status == "suspended" else None, suspension_reason="Équipement indisponible" if status == "suspended" else None,
room_name_snapshot=room.name if room else None, room_name_snapshot=room.name if room else None,

View file

@ -9,6 +9,7 @@ from ..core.models.maintenance import Lot, LotTask
from ..core.models.planning import ScheduledTask from ..core.models.planning import ScheduledTask
from ..core.services.maintenance_engine import generate_due_tasks from ..core.services.maintenance_engine import generate_due_tasks
from ..core.models.company import Company from ..core.models.company import Company
from ..contracts.models import Contract
from ..core.models.equipment import Equipment from ..core.models.equipment import Equipment
lots_bp = Blueprint('lots', __name__) lots_bp = Blueprint('lots', __name__)
@ -71,6 +72,8 @@ def _apply_task_form(task):
task.after_repair_policy = request.form.get('after_repair_policy') or 'preserve' task.after_repair_policy = request.form.get('after_repair_policy') or 'preserve'
task.is_safety_critical = request.form.get('is_safety_critical') == 'on' task.is_safety_critical = request.form.get('is_safety_critical') == 'on'
task.is_active = request.form.get('is_active') == 'on' task.is_active = request.form.get('is_active') == 'on'
task.company_id = request.form.get('company_id', type=int) or None
task.contract_id = request.form.get('contract_id', type=int) or None
@lots_bp.route('/<int:lot_id>/tasks/new', methods=['GET', 'POST']) @lots_bp.route('/<int:lot_id>/tasks/new', methods=['GET', 'POST'])
@ -87,7 +90,7 @@ def task_create(lot_id):
db.session.commit() db.session.commit()
flash('Tâche préventive ajoutée au lot.', 'success') flash('Tâche préventive ajoutée au lot.', 'success')
return redirect(url_for('lots.detail', id=lot.id)) return redirect(url_for('lots.detail', id=lot.id))
return render_template('lots/task_form.html', lot=lot, task=task, creating=True) return render_template('lots/task_form.html', lot=lot, task=task, creating=True, companies=Company.query.order_by(Company.name).all(), contracts=Contract.query.order_by(Contract.name).all())
@lots_bp.route('/<int:lot_id>/tasks/<int:task_id>/edit', methods=['GET', 'POST']) @lots_bp.route('/<int:lot_id>/tasks/<int:task_id>/edit', methods=['GET', 'POST'])
@ -103,7 +106,7 @@ def task_edit(lot_id, task_id):
db.session.commit() db.session.commit()
flash('Règle préventive mise à jour.', 'success') flash('Règle préventive mise à jour.', 'success')
return redirect(url_for('lots.detail', id=lot.id)) return redirect(url_for('lots.detail', id=lot.id))
return render_template('lots/task_form.html', lot=lot, task=task, creating=False) return render_template('lots/task_form.html', lot=lot, task=task, creating=False, companies=Company.query.order_by(Company.name).all(), contracts=Contract.query.order_by(Contract.name).all())
@lots_bp.route('/<int:lot_id>/generate', methods=['POST']) @lots_bp.route('/<int:lot_id>/generate', methods=['POST'])

View file

@ -36,7 +36,7 @@
<td>{{ task.scheduled_date.strftime('%d/%m/%Y') if task.scheduled_date else '-' }}</td> <td>{{ task.scheduled_date.strftime('%d/%m/%Y') if task.scheduled_date else '-' }}</td>
<td>{{ task.scheduled_start.strftime('%H:%M') if task.scheduled_start else '- - -' }}</td> <td>{{ task.scheduled_start.strftime('%H:%M') if task.scheduled_start else '- - -' }}</td>
<td><strong>{{ task.lot_task.tache if task.lot_task else '-' }}</strong></td> <td><strong>{{ task.lot_task.tache if task.lot_task else '-' }}</strong></td>
<td>{{ task.equipment.name if task.equipment else '-' }}</td> <td>{{ task.equipment.name if task.equipment else '-' }}{% if task.company %}<div class="small text-primary"><i class="bi bi-building"></i> {{ task.company.name }}{% if task.contract %} · {{ task.contract.name }}{% endif %}</div>{% endif %}</td>
<td>{{ task.room.name if task.room else '-' }}</td> <td>{{ task.room.name if task.room else '-' }}</td>
<td>{% if task.estimated_duration %}{{ task.estimated_duration }}{% elif task.lot_task and task.lot_task.duree_minutes %}{{ task.lot_task.duree_minutes }}{% else %}-{% endif %} min</td> <td>{% if task.estimated_duration %}{{ task.estimated_duration }}{% elif task.lot_task and task.lot_task.duree_minutes %}{{ task.lot_task.duree_minutes }}{% else %}-{% endif %} min</td>
<td> <td>

View file

@ -21,6 +21,8 @@
<div class="col-md-6"><label class="form-label">Portée</label><select class="form-select" name="application_scope"><option value="auto" {% if task.application_scope == 'auto' %}selected{% endif %}>Automatique selon le type déquipement</option><option value="group" {% if task.application_scope == 'group' %}selected{% endif %}>Une tâche par groupe/localisation</option><option value="unit" {% if task.application_scope == 'unit' %}selected{% endif %}>Une tâche par unité individualisée</option></select><div class="form-text">Pour 30 chaises, « groupe » crée une seule tâche dans la salle.</div></div> <div class="col-md-6"><label class="form-label">Portée</label><select class="form-select" name="application_scope"><option value="auto" {% if task.application_scope == 'auto' %}selected{% endif %}>Automatique selon le type déquipement</option><option value="group" {% if task.application_scope == 'group' %}selected{% endif %}>Une tâche par groupe/localisation</option><option value="unit" {% if task.application_scope == 'unit' %}selected{% endif %}>Une tâche par unité individualisée</option></select><div class="form-text">Pour 30 chaises, « groupe » crée une seule tâche dans la salle.</div></div>
<div class="col-md-6"><label class="form-label">Si léquipement est indisponible</label><select class="form-select" name="unavailable_policy"><option value="suspend" {% if task.unavailable_policy == 'suspend' %}selected{% endif %}>Suspendre et reprendre après réparation</option><option value="keep" {% if task.unavailable_policy == 'keep' %}selected{% endif %}>Maintenir la tâche</option><option value="postpone" {% if task.unavailable_policy == 'postpone' %}selected{% endif %}>Ne pas générer avant remise en service</option></select></div> <div class="col-md-6"><label class="form-label">Si léquipement est indisponible</label><select class="form-select" name="unavailable_policy"><option value="suspend" {% if task.unavailable_policy == 'suspend' %}selected{% endif %}>Suspendre et reprendre après réparation</option><option value="keep" {% if task.unavailable_policy == 'keep' %}selected{% endif %}>Maintenir la tâche</option><option value="postpone" {% if task.unavailable_policy == 'postpone' %}selected{% endif %}>Ne pas générer avant remise en service</option></select></div>
<div class="col-md-6"><label class="form-label">Après réparation</label><select class="form-select" name="after_repair_policy"><option value="preserve" {% if task.after_repair_policy == 'preserve' %}selected{% endif %}>Conserver léchéance dorigine</option><option value="reset" {% if task.after_repair_policy == 'reset' %}selected{% endif %}>Repartir de la date de réparation</option></select></div> <div class="col-md-6"><label class="form-label">Après réparation</label><select class="form-select" name="after_repair_policy"><option value="preserve" {% if task.after_repair_policy == 'preserve' %}selected{% endif %}>Conserver léchéance dorigine</option><option value="reset" {% if task.after_repair_policy == 'reset' %}selected{% endif %}>Repartir de la date de réparation</option></select></div>
<div class="col-md-6"><label class="form-label">Entreprise exécutante</label><select class="form-select" name="company_id"><option value="">Interne / non définie</option>{% for company in companies %}<option value="{{ company.id }}" {% if task.company_id == company.id %}selected{% endif %}>{{ company.name }}</option>{% endfor %}</select></div>
<div class="col-md-6"><label class="form-label">Contrat associé</label><select class="form-select" name="contract_id"><option value="">Aucun contrat</option>{% for contract in contracts %}<option value="{{ contract.id }}" {% if task.contract_id == contract.id %}selected{% endif %}>{{ contract.name }}{% if contract.contract_number %} — {{ contract.contract_number }}{% endif %}</option>{% endfor %}</select></div>
<div class="col-md-3 form-check ms-2 mt-5"><input class="form-check-input" type="checkbox" name="is_safety_critical" id="safety" {% if task.is_safety_critical %}checked{% endif %}><label class="form-check-label" for="safety">Tâche de sécurité</label></div> <div class="col-md-3 form-check ms-2 mt-5"><input class="form-check-input" type="checkbox" name="is_safety_critical" id="safety" {% if task.is_safety_critical %}checked{% endif %}><label class="form-check-label" for="safety">Tâche de sécurité</label></div>
<div class="col-md-2 form-check ms-2 mt-5"><input class="form-check-input" type="checkbox" name="is_active" id="active" {% if task.is_active %}checked{% endif %}><label class="form-check-label" for="active">Active</label></div> <div class="col-md-2 form-check ms-2 mt-5"><input class="form-check-input" type="checkbox" name="is_active" id="active" {% if task.is_active %}checked{% endif %}><label class="form-check-label" for="active">Active</label></div>
</div> </div>

View file

@ -0,0 +1,13 @@
"""Lie les tâches préventives aux entreprises et contrats."""
from alembic import op
import sqlalchemy as sa
revision = "fc3d4e5f6a7b"
down_revision = "fb2c3d4e5f6a"
branch_labels = None
depends_on = None
def upgrade():
op.add_column("lot_tasks", sa.Column("company_id", sa.Integer(), sa.ForeignKey("companies.id"), nullable=True))
op.add_column("lot_tasks", sa.Column("contract_id", sa.Integer(), sa.ForeignKey("contracts.id"), nullable=True))
op.add_column("scheduled_tasks", sa.Column("contract_id", sa.Integer(), sa.ForeignKey("contracts.id"), nullable=True))
def downgrade():
op.drop_column("scheduled_tasks", "contract_id"); op.drop_column("lot_tasks", "contract_id"); op.drop_column("lot_tasks", "company_id")