Ajouter le suivi des demandes de travaux
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
67ad5c568b
commit
a65387361f
8 changed files with 102 additions and 2 deletions
|
|
@ -138,6 +138,8 @@ def create_app(config_name='default'):
|
|||
app.register_blueprint(housing_bp, url_prefix='/housing')
|
||||
from .prevention import prevention_bp
|
||||
app.register_blueprint(prevention_bp, url_prefix='/prevention')
|
||||
from .works import works_bp
|
||||
app.register_blueprint(works_bp, url_prefix='/works')
|
||||
|
||||
# Blueprints des intégrations
|
||||
from .pronote.routes import pronote_bp
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ from .college import College, Site, Building, HousingUnit, HousingOccupancy, Roo
|
|||
from .equipment import EquipmentCategory, Equipment, EquipmentDocument, EquipmentRoomHistory, EquipmentQuantityMovement, EquipmentLifecycleEvent
|
||||
from .maintenance import (
|
||||
Intervention, StatusChange, InterventionComment, InterventionDocument,
|
||||
InterventionPart, Lot, LotTask, LotService
|
||||
InterventionPart, Lot, LotTask, LotService, WorkRequest, WorkRequestQuote, WorkRequestEvent
|
||||
)
|
||||
from .company import Company, Part, PartStockMovement, Alert, Service
|
||||
from .planning import (
|
||||
|
|
@ -25,7 +25,7 @@ __all__ = [
|
|||
'College', 'Site', 'Building', 'HousingUnit', 'HousingOccupancy', 'Room', 'RoomType', 'RoomSchedule', 'Zone',
|
||||
'EquipmentCategory', 'Equipment', 'EquipmentDocument', 'EquipmentRoomHistory', 'EquipmentQuantityMovement', 'EquipmentLifecycleEvent',
|
||||
'Intervention', 'StatusChange', 'InterventionComment', 'InterventionDocument',
|
||||
'Lot', 'LotTask', 'LotService',
|
||||
'Lot', 'LotTask', 'LotService', 'WorkRequest', 'WorkRequestQuote', 'WorkRequestEvent',
|
||||
'Company', 'Part', 'PartStockMovement', 'InterventionPart', 'Alert', 'Service',
|
||||
'WorkSchedule', 'WorkScheduleTemplate', 'AnnualTimeConfig', 'TimeEntry', 'CollegeClosure', 'ClosureSchedule', 'ClosureWorkDay', 'PersonalLeave', 'Training', 'TrainingParticipant',
|
||||
'Meter', 'MeterReading', 'Consumable', 'ConsumableUsage', 'EquipmentConsumable',
|
||||
|
|
|
|||
|
|
@ -415,6 +415,68 @@ class LotTask(db.Model):
|
|||
return f"<LotTask {self.num_tache or self.id}>"
|
||||
|
||||
|
||||
class WorkRequest(db.Model):
|
||||
"""Demande de travaux suivie en interne par le technicien."""
|
||||
__tablename__ = "work_requests"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
title = db.Column(db.String(255), nullable=False)
|
||||
description = db.Column(db.Text, nullable=False)
|
||||
status = db.Column(db.String(40), nullable=False, default="brouillon", index=True)
|
||||
execution_mode = db.Column(db.String(30), nullable=False, default="interne")
|
||||
priority = db.Column(db.String(20), nullable=False, default="normale")
|
||||
equipment_id = db.Column(db.Integer, db.ForeignKey("equipments.id"), nullable=True)
|
||||
service_id = db.Column(db.Integer, db.ForeignKey("services.id"), nullable=True)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey("companies.id"), nullable=True)
|
||||
created_by_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
|
||||
chief_decision = db.Column(db.String(20), nullable=True)
|
||||
department_decision = db.Column(db.String(20), nullable=True)
|
||||
chief_decision_date = db.Column(db.Date, nullable=True)
|
||||
department_decision_date = db.Column(db.Date, nullable=True)
|
||||
planned_date = db.Column(db.Date, nullable=True)
|
||||
completed_date = db.Column(db.Date, nullable=True)
|
||||
actual_minutes = db.Column(db.Integer, nullable=True)
|
||||
parts_cost = db.Column(db.Numeric(10, 2), nullable=True)
|
||||
notes = db.Column(db.Text, nullable=True)
|
||||
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||
updated_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc))
|
||||
equipment = db.relationship("Equipment")
|
||||
service = db.relationship("Service")
|
||||
company = db.relationship("Company")
|
||||
created_by = db.relationship("User")
|
||||
quotes = db.relationship("WorkRequestQuote", back_populates="request", cascade="all, delete-orphan")
|
||||
events = db.relationship("WorkRequestEvent", back_populates="request", cascade="all, delete-orphan", order_by="WorkRequestEvent.created_at")
|
||||
|
||||
|
||||
class WorkRequestQuote(db.Model):
|
||||
__tablename__ = "work_request_quotes"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
request_id = db.Column(db.Integer, db.ForeignKey("work_requests.id", ondelete="CASCADE"), nullable=False)
|
||||
company_id = db.Column(db.Integer, db.ForeignKey("companies.id"), nullable=True)
|
||||
reference = db.Column(db.String(100), nullable=True)
|
||||
amount_ht = db.Column(db.Numeric(10, 2), nullable=True)
|
||||
amount_ttc = db.Column(db.Numeric(10, 2), nullable=True)
|
||||
quote_date = db.Column(db.Date, nullable=True)
|
||||
valid_until = db.Column(db.Date, nullable=True)
|
||||
status = db.Column(db.String(20), nullable=False, default="en_attente")
|
||||
file_path = db.Column(db.String(500), nullable=True)
|
||||
notes = db.Column(db.Text, nullable=True)
|
||||
request = db.relationship("WorkRequest", back_populates="quotes")
|
||||
company = db.relationship("Company")
|
||||
|
||||
|
||||
class WorkRequestEvent(db.Model):
|
||||
__tablename__ = "work_request_events"
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
request_id = db.Column(db.Integer, db.ForeignKey("work_requests.id", ondelete="CASCADE"), nullable=False)
|
||||
user_id = db.Column(db.Integer, db.ForeignKey("users.id"), nullable=False)
|
||||
old_status = db.Column(db.String(40), nullable=True)
|
||||
new_status = db.Column(db.String(40), nullable=False)
|
||||
comment = db.Column(db.Text, nullable=True)
|
||||
created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc))
|
||||
request = db.relationship("WorkRequest", back_populates="events")
|
||||
user = db.relationship("User")
|
||||
|
||||
|
||||
class LotService(db.Model):
|
||||
"""Association entre lots et services."""
|
||||
__tablename__ = "lot_services"
|
||||
|
|
|
|||
1
app_new/templates/works/detail.html
Normal file
1
app_new/templates/works/detail.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
{% extends "base.html" %}{% block title %}Demande de travaux{% endblock %}{% block content %}<div class="container-fluid"><h1 class="h3">{{ item.title }}</h1><p>{{ item.description }}</p><form method="post" class="card card-body mb-3"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><label>Statut</label><select class="form-select mb-2" name="status">{% for status in statuses %}<option {% if item.status == status %}selected{% endif %}>{{ status }}</option>{% endfor %}</select><label>Entreprise</label><select class="form-select mb-2" name="company_id"><option value="">—</option>{% for x in companies %}<option value="{{ x.id }}" {% if item.company_id == x.id %}selected{% endif %}>{{ x.name }}</option>{% endfor %}</select><label>Service Département</label><select class="form-select mb-2" name="service_id"><option value="">—</option>{% for x in services %}<option value="{{ x.id }}" {% if item.service_id == x.id %}selected{% endif %}>{{ x.name }}</option>{% endfor %}</select><textarea class="form-control mb-2" name="comment" placeholder="Commentaire de suivi"></textarea><button class="btn btn-primary">Enregistrer</button></form><h2 class="h5">Historique</h2><ul>{% for event in item.events %}<li>{{ event.created_at|date_fmt }} — {{ event.new_status }}{% if event.comment %} : {{ event.comment }}{% endif %}</li>{% endfor %}</ul></div>{% endblock %}
|
||||
1
app_new/templates/works/form.html
Normal file
1
app_new/templates/works/form.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
{% extends "base.html" %}{% block title %}Nouvelle demande{% endblock %}{% block content %}<div class="container"><h1 class="h3">Nouvelle demande de travaux</h1><form method="post" class="card card-body"><input type="hidden" name="csrf_token" value="{{ csrf_token() }}"><label>Titre</label><input class="form-control mb-2" name="title" required><label>Description</label><textarea class="form-control mb-2" name="description" required></textarea><label>Réalisation</label><select class="form-select mb-2" name="execution_mode"><option value="interne">Moi-même</option><option value="devis">Entreprise avec devis</option><option value="departement">Département</option></select><label>Équipement</label><select class="form-select mb-3" name="equipment_id"><option value="">—</option>{% for e in equipments %}<option value="{{ e.id }}">{{ e.name }}</option>{% endfor %}</select><button class="btn btn-primary">Créer la demande</button></form></div>{% endblock %}
|
||||
1
app_new/templates/works/index.html
Normal file
1
app_new/templates/works/index.html
Normal file
|
|
@ -0,0 +1 @@
|
|||
{% extends "base.html" %}{% block title %}Demandes de travaux{% endblock %}{% block content %}<div class="container-fluid"><div class="d-flex justify-content-between mb-3"><h1 class="h3">Demandes de travaux</h1><a class="btn btn-primary" href="{{ url_for('works.new') }}">Nouvelle demande</a></div><table class="table table-sm"><thead><tr><th>Demande</th><th>Mode</th><th>Statut</th><th>Date</th></tr></thead><tbody>{% for item in requests %}<tr><td><a href="{{ url_for('works.detail', id=item.id) }}">{{ item.title }}</a></td><td>{{ item.execution_mode }}</td><td>{{ item.status }}</td><td>{{ item.created_at|date_fmt }}</td></tr>{% else %}<tr><td colspan="4">Aucune demande.</td></tr>{% endfor %}</tbody></table></div>{% endblock %}
|
||||
1
app_new/works/__init__.py
Normal file
1
app_new/works/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
from .routes import works_bp
|
||||
32
app_new/works/routes.py
Normal file
32
app_new/works/routes.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
from datetime import date
|
||||
from flask import Blueprint, flash, redirect, render_template, request, url_for
|
||||
from flask_login import current_user, login_required
|
||||
from ..extensions import db
|
||||
from ..core.models import WorkRequest, WorkRequestEvent, WorkRequestQuote, Equipment, Company, Service
|
||||
|
||||
works_bp = Blueprint("works", __name__, template_folder="templates")
|
||||
|
||||
STATUSES = ["brouillon", "à_valider_chef", "travaux_internes_autorisés", "devis_en_attente", "devis_à_valider_chef", "devis_accepté", "envoyée_département", "entreprise_mandatée", "travaux_planifiés", "travaux_en_cours", "travaux_terminés", "clôturée", "refusée"]
|
||||
|
||||
@works_bp.route("/")
|
||||
@login_required
|
||||
def index():
|
||||
return render_template("works/index.html", requests=WorkRequest.query.order_by(WorkRequest.created_at.desc()).all())
|
||||
|
||||
@works_bp.route("/new", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def new():
|
||||
if request.method == "POST":
|
||||
item = WorkRequest(title=request.form["title"].strip(), description=request.form["description"].strip(), execution_mode=request.form.get("execution_mode", "interne"), priority=request.form.get("priority", "normale"), equipment_id=request.form.get("equipment_id", type=int) or None, created_by_id=current_user.id, status="à_valider_chef")
|
||||
db.session.add(item); db.session.flush(); db.session.add(WorkRequestEvent(request=item, user_id=current_user.id, new_status=item.status, comment="Demande créée")); db.session.commit(); flash("Demande de travaux créée.", "success"); return redirect(url_for("works.detail", id=item.id))
|
||||
return render_template("works/form.html", equipments=Equipment.query.filter_by(is_deleted=False).order_by(Equipment.name).all())
|
||||
|
||||
@works_bp.route("/<int:id>", methods=["GET", "POST"])
|
||||
@login_required
|
||||
def detail(id):
|
||||
item = WorkRequest.query.get_or_404(id)
|
||||
if request.method == "POST":
|
||||
old = item.status; item.status = request.form.get("status", old); item.notes = request.form.get("notes") or item.notes; item.company_id = request.form.get("company_id", type=int) or item.company_id; item.service_id = request.form.get("service_id", type=int) or item.service_id
|
||||
if item.status != old: db.session.add(WorkRequestEvent(request=item, user_id=current_user.id, old_status=old, new_status=item.status, comment=request.form.get("comment")))
|
||||
db.session.commit(); flash("Demande mise à jour.", "success"); return redirect(url_for("works.detail", id=id))
|
||||
return render_template("works/detail.html", item=item, statuses=STATUSES, companies=Company.query.order_by(Company.name).all(), services=Service.query.order_by(Service.name).all())
|
||||
Loading…
Reference in a new issue