loko/streetup/stock/templates/stock/serialized_item_detail.html
2026-07-22 14:48:40 +02:00

296 lines
15 KiB
HTML

{% extends "base.html" %}
{% load i18n %}
{% block content %}
<div class="container-fluid mt-3">
<!-- En-tête -->
<div class="d-flex justify-content-between align-items-center mb-3">
<h3 class="h3 d-flex align-items-center gap-2">
<i class="bi bi-upc-scan"></i>
<span class="font-monospace">{{ item.serial_number }}</span>
{% if not item.is_active %}
<span class="badge bg-danger ms-2"><i class="bi bi-x-circle me-1"></i>{% translate "Inactif" %}</span>
{% endif %}
</h3>
<div class="d-flex gap-2">
{% if can_manage and item.is_active %}
<button type="button" class="btn btn-outline-danger"
data-bs-toggle="modal" data-bs-target="#deleteItemModal">
<i class="bi bi-trash"></i> {% translate "Supprimer" %}
</button>
{% endif %}
<a href="{% url 'stock:serialized_items_list' %}" class="btn btn-light">
<i class="bi bi-arrow-left"></i> {% translate "Retour" %}
</a>
</div>
</div>
{% if can_manage and item.is_active %}
<!-- Modal suppression -->
<div class="modal fade" id="deleteItemModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title text-danger">
<i class="bi bi-exclamation-triangle"></i>
{% translate "Confirmer la désactivation" %}
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
{% blocktranslate with sn=item.serial_number %}
Êtes-vous sûr de vouloir rendre l'article <strong>{{ sn }}</strong> inactif ?
Il ne sera plus visible pour les nouvelles opérations de stock, mais restera accessible dans l'historique.
{% endblocktranslate %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
{% translate "Annuler" %}
</button>
<form method="post" action="{% url 'stock:serialized_item_delete' item.pk %}">
{% csrf_token %}
<button type="submit" class="btn btn-danger">
<i class="bi bi-x-circle me-1"></i> {% translate "Rendre inactif" %}
</button>
</form>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Cartes d'informations -->
<div class="row g-3 mb-4">
<div class="col-md-3">
<div class="card h-100">
<div class="card-body">
<h6 class="card-subtitle mb-2 text-muted"><i class="bi bi-box-seam me-1"></i>{% translate "Produit" %}</h6>
<p class="card-text fs-5 mb-1">
<a href="{% url 'stock:product_detail' item.product.pk %}">{{ item.product.name }}</a>
</p>
<small class="text-muted">{{ item.product.code }}</small>
</div>
</div>
</div>
<div class="col-md-2">
<div class="card h-100">
<div class="card-body text-center">
<h6 class="card-subtitle mb-2 text-muted">{% translate "Statut" %}</h6>
{% if item.status == 'available' %}
<span class="badge bg-success fs-6">{{ item.get_status_display }}</span>
{% elif item.status == 'assigned' %}
<span class="badge bg-primary fs-6">{{ item.get_status_display }}</span>
{% elif item.status == 'lost' %}
<span class="badge bg-danger fs-6">{{ item.get_status_display }}</span>
{% else %}
<span class="badge bg-secondary fs-6">{{ item.get_status_display }}</span>
{% endif %}
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100">
<div class="card-body">
<h6 class="card-subtitle mb-2 text-muted"><i class="bi bi-person me-1"></i>{% translate "Détenteur actuel" %}</h6>
{% if item.current_holder %}
<p class="card-text fs-5 mb-0">{{ item.current_holder.name }}</p>
<small class="text-muted">{{ item.current_holder.teams_display|default:"—" }}</small>
{% else %}
<p class="card-text text-muted fst-italic">{% translate "Aucun" %}</p>
{% endif %}
</div>
</div>
</div>
<div class="col-md-3">
<div class="card h-100">
<div class="card-body">
<h6 class="card-subtitle mb-2 text-muted"><i class="bi bi-geo-alt me-1"></i>{% translate "Emplacement actuel" %}</h6>
{% if item.current_location %}
<p class="card-text fs-5 mb-0">{{ item.current_location.name }}</p>
<small class="text-muted">{{ item.current_location.warehouse.name }}</small>
{% else %}
<p class="card-text text-muted fst-italic">{% translate "Non renseigné" %}</p>
{% endif %}
</div>
</div>
</div>
{% if item.qr_code %}
<div class="col-md-1">
<div class="card h-100">
<div class="card-body text-center p-2">
<h6 class="card-subtitle mb-2 text-muted small">{% translate "QR Code" %}</h6>
<a href="{{ item.qr_code.url }}" target="_blank" title="{% translate 'Ouvrir le QR code' %}">
<img src="{{ item.qr_code.url }}" alt="QR Code {{ item.serial_number }}" class="img-fluid">
</a>
</div>
</div>
</div>
{% endif %}
</div>
{% if item.notes %}
<div class="alert alert-light border mb-4">
<strong><i class="bi bi-sticky me-1"></i>{% translate "Notes" %} :</strong> {{ item.notes }}
</div>
{% endif %}
<!-- Formulaire d'événement -->
{% if can_manage and item.status != 'retired' and item.is_active %}
<div class="card mb-4 border-primary">
<div class="card-header bg-primary text-white">
<h5 class="mb-0"><i class="bi bi-plus-circle me-1"></i>{% translate "Enregistrer un événement" %}</h5>
</div>
<div class="card-body">
<form method="post" action="{% url 'stock:serialized_item_event_create' item.pk %}">
{% csrf_token %}
<div class="row g-3">
<div class="col-md-3">
<label class="form-label fw-semibold">{% translate "Type d'événement" %} *</label>
<select name="event_type" class="form-select" id="event_type_select" required>
<option value="">— {% translate "Choisir" %} —</option>
<option value="assigned">{% translate "Attribué" %}</option>
<option value="returned">{% translate "Rendu" %}</option>
<option value="transferred">{% translate "Transféré" %}</option>
<option value="lost">{% translate "Perdu" %}</option>
<option value="retired">{% translate "Retiré du service" %}</option>
<option value="note">{% translate "Note" %}</option>
</select>
</div>
<div class="col-md-3" id="field_person">
<label class="form-label fw-semibold">{% translate "Personne" %}</label>
<select name="person" class="form-select">
<option value="">— {% translate "Aucune" %} —</option>
{% for member in members %}
<option value="{{ member.pk }}" {% if item.current_holder_id == member.pk %}selected{% endif %}>
{{ member.name }}{% if member.teams.exists %} ({{ member.teams_display }}){% endif %}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-3" id="field_to_location">
<label class="form-label fw-semibold">{% translate "Emplacement de destination" %}</label>
<select name="to_location" class="form-select">
<option value="">— {% translate "Aucun" %} —</option>
{% for loc in locations %}
<option value="{{ loc.pk }}">{{ loc.warehouse.name }} — {{ loc.name }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-12">
<label class="form-label fw-semibold">{% translate "Remarques" %}</label>
<input type="text" name="notes" class="form-control" placeholder="{% translate 'Remarques optionnelles…' %}">
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary">
<i class="bi bi-check2"></i> {% translate "Enregistrer" %}
</button>
</div>
</div>
</form>
</div>
</div>
{% endif %}
<!-- Historique -->
<div class="card">
<div class="card-header">
<h5 class="mb-0"><i class="bi bi-clock-history me-1"></i>{% translate "Historique" %}</h5>
</div>
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-sm mb-0">
<thead class="table-light">
<tr>
<th>{% translate "Date" %}</th>
<th>{% translate "Événement" %}</th>
<th>{% translate "Personne" %}</th>
<th>{% translate "Depuis" %}</th>
<th>{% translate "Vers" %}</th>
<th>{% translate "Remarques" %}</th>
<th>{% translate "Enregistré par" %}</th>
</tr>
</thead>
<tbody>
{% for event in events %}
<tr>
<td class="text-nowrap">{{ event.date|date:"d/m/Y H:i" }}</td>
<td>
{% if event.event_type == 'entry' %}
<span class="badge bg-info text-dark">{{ event.get_event_type_display }}</span>
{% elif event.event_type == 'assigned' %}
<span class="badge bg-primary">{{ event.get_event_type_display }}</span>
{% elif event.event_type == 'returned' %}
<span class="badge bg-success">{{ event.get_event_type_display }}</span>
{% elif event.event_type == 'transferred' %}
<span class="badge bg-warning text-dark">{{ event.get_event_type_display }}</span>
{% elif event.event_type == 'lost' %}
<span class="badge bg-danger">{{ event.get_event_type_display }}</span>
{% elif event.event_type == 'retired' %}
<span class="badge bg-secondary">{{ event.get_event_type_display }}</span>
{% else %}
<span class="badge bg-light text-dark border">{{ event.get_event_type_display }}</span>
{% endif %}
</td>
<td>{{ event.person.name|default:"—" }}</td>
<td>
{% if event.from_location %}
{{ event.from_location.name }}
<small class="text-muted d-block">{{ event.from_location.warehouse.name }}</small>
{% else %}—{% endif %}
</td>
<td>
{% if event.to_location %}
{{ event.to_location.name }}
<small class="text-muted d-block">{{ event.to_location.warehouse.name }}</small>
{% else %}—{% endif %}
</td>
<td>{{ event.notes|default:"—" }}</td>
<td class="text-muted small">{{ event.created_by.get_full_name|default:event.created_by.username|default:"—" }}</td>
</tr>
{% empty %}
<tr>
<td colspan="7" class="text-center text-muted py-3">{% translate "Aucun événement enregistré." %}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<p class="text-muted small mt-2">
{% translate "Enregistré le" %} {{ item.created_at|date:"d/m/Y H:i" }}
{% if item.created_by %}{% translate "par" %} {{ item.created_by.get_full_name|default:item.created_by.username }}{% endif %}
</p>
</div>
<script>
document.addEventListener("DOMContentLoaded", function() {
const locationSelect = document.querySelector('select[name="to_location"]');
if (locationSelect) {
const originalOptions = Array.from(locationSelect.options);
const apiBaseUrl = "{% url 'stock:product_locations_api' item.product.id %}";
locationSelect.disabled = true;
fetch(apiBaseUrl)
.then(response => response.json())
.then(data => {
const allowedIds = new Set(data.locations.map(loc => loc.id.toString()));
locationSelect.innerHTML = "";
originalOptions.forEach(opt => {
if (opt.value === "" || allowedIds.has(opt.value)) {
locationSelect.appendChild(opt.cloneNode(true));
}
});
locationSelect.disabled = false;
})
.catch(error => {
console.error("Error fetching locations:", error);
locationSelect.disabled = false;
});
}
});
</script>
{% endblock content %}