diff --git a/app_new/__init__.py b/app_new/__init__.py index fe53efe..f9449f2 100644 --- a/app_new/__init__.py +++ b/app_new/__init__.py @@ -154,12 +154,18 @@ def create_app(config_name='default'): from .room_types.routes import room_types_bp app.register_blueprint(room_types_bp, url_prefix='/room-types') + + from .room_profiles.routes import room_profiles_bp + app.register_blueprint(room_profiles_bp, url_prefix='/room-profiles') from .documents.routes import documents_bp app.register_blueprint(documents_bp, url_prefix='/documents') from .gmao_config.routes import gmao_config_bp app.register_blueprint(gmao_config_bp, url_prefix='/gmao-config') + + from .configuration.routes import configuration_bp + app.register_blueprint(configuration_bp, url_prefix='/configuration') from .wizard.routes import wizard_bp app.register_blueprint(wizard_bp) diff --git a/app_new/configuration/__init__.py b/app_new/configuration/__init__.py new file mode 100644 index 0000000..57775d8 --- /dev/null +++ b/app_new/configuration/__init__.py @@ -0,0 +1 @@ +"""Centre de configuration permanent.""" diff --git a/app_new/configuration/routes.py b/app_new/configuration/routes.py new file mode 100644 index 0000000..6922f4d --- /dev/null +++ b/app_new/configuration/routes.py @@ -0,0 +1,69 @@ +from flask import Blueprint, render_template +from flask_login import login_required + +from app_new.core.models.college import Site, Building, Zone, Room, RoomProfile, RoomSchedule +from app_new.core.models.equipment import Equipment +from app_new.core.models.maintenance import LotTask +from app_new.core.models.user import User +from app_new.core.models.planning import WorkSchedule +from app_new.contracts.models import Contract + +configuration_bp = Blueprint("configuration", __name__, template_folder="templates") + + +def _status(ok, label, url, detail=None, optional=False, attention=False): + if optional and not ok: + state = "Facultatif" + elif attention: + state = "Attention" + elif ok: + state = "Configuré" + else: + state = "À configurer" + return {"label": label, "state": state, "ok": ok, "url": url, "detail": detail or ""} + + +@configuration_bp.route("/") +@login_required +def index(): + from flask import url_for + sites = Site.query.count() + buildings = Building.query.count() + zones = Zone.query.count() + rooms = Room.query.count() + profiles = RoomProfile.query.count() + unprofiled = Room.query.filter(Room.room_type_id.is_(None)).count() + equipment_without_lot = Equipment.query.filter(Equipment.is_deleted.is_(False), Equipment.lot_id.is_(None)).count() + technicians = User.query.filter_by(is_active=True).all() + scheduled_users = {row[0] for row in WorkSchedule.query.filter_by(is_active=True).with_entities(WorkSchedule.user_id).all()} + tasks_without_duration = LotTask.query.filter(LotTask.is_active.is_(True), (LotTask.duree_minutes.is_(None) | (LotTask.duree_minutes <= 0))).count() + contracts_without_due = Contract.query.filter(Contract.is_active.is_(True), Contract.end_date.is_(None)).count() if hasattr(Contract, "end_date") else 0 + room_schedules = RoomSchedule.query.count() + sections = [ + ("ESSENTIEL", [ + _status(User.query.filter_by(is_active=True).count() > 0, "Administrateur / utilisateurs", url_for("admin.users")), + _status(sites > 0, "Établissement / site", url_for("buildings.index"), f"{sites} site(s)"), + _status(buildings > 0, "Bâtiments", url_for("buildings.index"), f"{buildings} bâtiment(s)"), + _status(zones > 0, "Zones", url_for("zones.index"), f"{zones} zone(s)"), + _status(rooms > 0, "Locaux / emplacements", url_for("rooms.index"), f"{rooms} local(aux)"), + _status(len(scheduled_users) >= len(technicians) and bool(technicians), "Horaires techniciens", url_for("planning.work_schedules"), f"{len(technicians) - len(scheduled_users)} sans horaires" if technicians else "Aucun technicien actif", attention=bool(technicians) and len(scheduled_users) < len(technicians)), + ]), + ("MAINTENANCE", [ + _status(profiles > 0, "Profils de locaux", url_for("room_profiles.index"), f"{profiles} profil(s)"), + _status(equipment_without_lot == 0, "Équipements avec lot", url_for("equipments.index"), f"{equipment_without_lot} sans lot", attention=equipment_without_lot > 0), + _status(tasks_without_duration == 0, "Durées préventives", url_for("lots.index"), f"{tasks_without_duration} à renseigner", attention=tasks_without_duration > 0), + _status(room_schedules > 0, "Planning des salles", url_for("planning.room_schedules"), f"{room_schedules} créneau(x)"), + ]), + ("PATRIMOINE", [ + _status(unprofiled == 0, "Locaux avec profil structurel", url_for("rooms.index"), f"{unprofiled} sans type", attention=unprofiled > 0), + _status(True, "Logements de fonction", url_for("housing.index"), "Facultatif", optional=True), + ]), + ("ENTREPRISES", [_status(contracts_without_due == 0, "Contrats avec échéance", url_for("companies.index"), f"{contracts_without_due} sans échéance", attention=contracts_without_due > 0)]), + ("COMPTEURS", [_status(False, "Compteurs", url_for("meters.index"), "Non configuré", optional=True)]), + ("CARTOGRAPHIE TECHNIQUE", [_status(False, "Relations techniques", url_for("equipments.index"), "Facultatif", optional=True)]), + ("INTÉGRATIONS", [ + _status(False, "Pronote", url_for("pronote.index"), "Non configuré", optional=True), + _status(False, "ENT / Outlook / Yeastar / IA", url_for("admin.settings"), "Facultatif", optional=True), + ]), + ] + return render_template("configuration/index.html", sections=sections) diff --git a/app_new/configuration/templates/configuration/index.html b/app_new/configuration/templates/configuration/index.html new file mode 100644 index 0000000..2c83efd --- /dev/null +++ b/app_new/configuration/templates/configuration/index.html @@ -0,0 +1,5 @@ +{% extends 'base.html' %} +{% block title %}Centre de configuration{% endblock %} +{% block content %} +
Un état permanent de ce qui est prêt, incomplet ou facultatif.
Un nom par ligne. Le profil de local reste modifiable ensuite ; aucun équipement n'est imposé.
Un nom par ligne. Le profil est associé sans créer automatiquement d'équipement.
Un profil propose des équipements et des ouvrages. Rien n'est créé avant validation dans la prévisualisation.
+ +{% if profile %}| Proposition | Qté | Mode | |
|---|---|---|---|
| {{ item.label }}{% if item.category %} · {{ item.category.name }}{% endif %} | {{ item.quantity }} | {{ 'Groupe' if item.is_group else 'Individuel' }} | |
| Aucune proposition. | |||
Sélectionnez des locaux puis validez chaque proposition dans l'écran de prévisualisation.
Prévisualiser l'applicationDes propositions réutilisables, jamais des créations automatiques.
{{ profile.description or 'Aucune description.' }}
Aucun équipement ne sera créé avant votre validation explicite.