Ajouter la vue des équipements par zones et salles
Some checks are pending
CI - Tests et Syntax / lint-and-test (push) Waiting to run

This commit is contained in:
root 2026-08-16 23:54:23 +00:00
parent 1725d672a9
commit 4783c34673
2 changed files with 52 additions and 3 deletions

View file

@ -51,6 +51,40 @@ def index():
status = request.args.get('status', '')
category_id = request.args.get('category', type=int)
building_id = request.args.get('building', type=int)
view = request.args.get('view', 'families')
if view == 'zones':
# Vue inventaire : une ligne par type et par salle, avec quantités
# agrégées. Les groupes parents sans salle sont ignorés afin de ne
# pas compter deux fois leurs groupes localisés.
grouped = {}
rows = Equipment.query.filter_by(is_deleted=False).all()
for eq in rows:
room = eq.room
if not room or (eq.is_group and eq.children.count()):
continue
if search and search.lower() not in (eq.name or '').lower():
continue
if category_id and eq.effective_category_id != category_id:
continue
if building_id and room.building_id != building_id:
continue
if status and eq.status != status:
continue
building = room.building
zone = room.zone
b = grouped.setdefault(building.id, {'name': building.name, 'zones': {}})
z = b['zones'].setdefault(zone.id if zone else 0, {'name': zone.name if zone else 'Sans zone', 'rooms': {}})
r = z['rooms'].setdefault(room.id, {'name': room.name, 'types': {}})
key = eq.effective_category_id or f'name:{eq.name.lower().split(" #")[0]}'
display_name = eq.category.name if eq.category else eq.name.split(' #')[0]
item = r['types'].setdefault(key, {'name': display_name, 'category': eq.category.name if eq.category else '', 'quantity': 0, 'ids': []})
item['quantity'] += eq.quantity or 1
item['ids'].append(eq.id)
zone_tree = [{'id': bid, 'name': b['name'], 'zones': [{'id': zid, 'name': z['name'], 'rooms': [{'id': rid, 'name': r['name'], 'types': list(r['types'].values())} for rid, r in z['rooms'].items()]} for zid, z in b['zones'].items()]} for bid, b in grouped.items()]
categories = EquipmentCategory.query.order_by(EquipmentCategory.name).all()
buildings = Building.query.order_by(Building.name).all()
return render_template('equipments/index.html', zone_tree=zone_tree, view=view, categories=categories, buildings=buildings, statuses=EQUIPMENT_STATUSES, filters={'search': search, 'status': status, 'category': category_id, 'building': building_id})
# Pagination (page courante, 20 parents par page)
page = request.args.get('page', 1, type=int)
@ -95,7 +129,7 @@ def index():
categories=categories,
buildings=buildings,
statuses=EQUIPMENT_STATUSES,
filters={'search': search, 'status': status, 'category': category_id, 'building': building_id})
filters={'search': search, 'status': status, 'category': category_id, 'building': building_id}, view=view)
@main_bp.route('/<int:id>/hierarchy')

View file

@ -7,7 +7,21 @@
<a href="{{ url_for('equipments.create') }}" class="btn btn-primary btn-sm">
<i class="bi bi-plus-lg"></i> <span class="d-none d-sm-inline">Nouvel</span><span class="d-sm-none">+</span>
</a>
<div class="btn-group btn-group-sm ms-2">
<a class="btn {{ 'btn-primary' if view != 'zones' else 'btn-outline-primary' }}" href="{{ url_for('equipments.index', view='families') }}"><i class="bi bi-diagram-3"></i> Par familles</a>
<a class="btn {{ 'btn-primary' if view == 'zones' else 'btn-outline-primary' }}" href="{{ url_for('equipments.index', view='zones') }}"><i class="bi bi-signpost-split"></i> Par zones et salles</a>
</div>
</div>
{% if view == 'zones' %}
<div class="alert alert-info py-2"><i class="bi bi-info-circle"></i> Les quantités sont regroupées par zone, salle et type déquipement. Les groupes parents sont exclus pour éviter les doublons.</div>
{% for building in zone_tree %}
<div class="card border-0 shadow-sm mb-3"><div class="card-header fw-semibold"><i class="bi bi-building"></i> {{ building.name }}</div><div class="card-body">
{% for zone in building.zones %}<div class="ms-2 mb-3"><h6><i class="bi bi-signpost-split"></i> {{ zone.name }}</h6>
{% for room in zone.rooms %}<div class="border rounded p-2 mb-2 ms-3"><strong><i class="bi bi-door-open"></i> {{ room.name }}</strong><div class="table-responsive"><table class="table table-sm mb-0 mt-1"><tbody>{% for item in room.types %}<tr><td>{{ item.name }}</td><td class="text-muted">{{ item.category }}</td><td class="text-end"><span class="badge bg-primary">{{ item.quantity }}</span></td></tr>{% endfor %}</tbody></table></div></div>{% endfor %}
</div>{% endfor %}</div></div>
{% else %}<div class="text-center text-muted py-4">Aucun équipement localisé ne correspond aux filtres.</div>{% endfor %}
{% else %}
<!-- Filtres -->
<div class="card border-0 shadow-sm mb-3">
@ -135,4 +149,5 @@
<p class="mt-2">Aucun équipement</p>
</div>
{% endif %}
{% endif %}
{% endblock %}