Ajouter la vue des équipements par zones et salles
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
1725d672a9
commit
4783c34673
2 changed files with 52 additions and 3 deletions
|
|
@ -51,7 +51,41 @@ 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)
|
||||
per_page = 20
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -7,8 +7,22 @@
|
|||
<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">
|
||||
<div class="card-body py-2">
|
||||
|
|
@ -135,4 +149,5 @@
|
|||
<p class="mt-2">Aucun équipement</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
|
|
|||
Loading…
Reference in a new issue