146 lines
6.8 KiB
HTML
146 lines
6.8 KiB
HTML
{% extends "base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block content %}
|
|
<div class="container mt-3" style="max-width: 700px;">
|
|
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
<h3 class="h3">
|
|
<i class="bi bi-upc-scan"></i>
|
|
{% translate "Enregistrer un article tracé" %}
|
|
</h3>
|
|
<a href="{% url 'stock:serialized_items_list' %}" class="btn btn-light">
|
|
<i class="bi bi-arrow-left"></i> {% translate "Retour" %}
|
|
</a>
|
|
</div>
|
|
|
|
<div class="card shadow-sm">
|
|
<div class="card-body">
|
|
<form method="post">
|
|
{% csrf_token %}
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">{% translate "Produit" %} *</label>
|
|
<select name="product" class="form-select" required>
|
|
<option value="">— {% translate "Sélectionner un produit" %} —</option>
|
|
{% for p in serialized_products %}
|
|
<option value="{{ p.pk }}"
|
|
{% if initial_product and initial_product.pk == p.pk %}selected
|
|
{% elif post.product == p.pk|stringformat:"s" %}selected{% endif %}>
|
|
{{ p.name }} ({{ p.code }})
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
{% if not serialized_products %}
|
|
<div class="form-text text-warning">
|
|
<i class="bi bi-exclamation-triangle"></i>
|
|
{% translate "Aucun produit n'est configuré pour le suivi par numéro de série. Activez l'option « Articles tracés » sur le produit souhaité." %}
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">{% translate "Numéro de série" %} *</label>
|
|
<input type="text" name="serial_number" class="form-control font-monospace"
|
|
value="{{ post.serial_number|default:'' }}"
|
|
placeholder="{% translate 'Ex: SN-2024-00123' %}" required>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">{% translate "Emplacement initial" %}</label>
|
|
<select name="current_location" class="form-select">
|
|
<option value="">— {% translate "Aucun" %} —</option>
|
|
{% for loc in locations %}
|
|
<option value="{{ loc.pk }}" {% if post.current_location == loc.pk|stringformat:"s" %}selected{% endif %}>
|
|
{{ loc.warehouse.name }} — {{ loc.name }}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">{% translate "Attribuer à (optionnel)" %}</label>
|
|
<select name="current_holder" class="form-select">
|
|
<option value="">— {% translate "Aucun" %} —</option>
|
|
{% for member in members %}
|
|
<option value="{{ member.pk }}" {% if post.current_holder == member.pk|stringformat:"s" %}selected{% endif %}>
|
|
{{ member.name }}{% if member.teams.exists %} ({{ member.teams_display }}){% endif %}
|
|
</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
|
|
<div class="mb-3">
|
|
<label class="form-label fw-semibold">{% translate "Notes" %}</label>
|
|
<textarea name="notes" class="form-control" rows="2"
|
|
placeholder="{% translate 'Notes optionnelles…' %}">{{ post.notes|default:'' }}</textarea>
|
|
</div>
|
|
|
|
<div class="d-flex gap-2">
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="bi bi-check2"></i> {% translate "Enregistrer" %}
|
|
</button>
|
|
<a href="{% url 'stock:serialized_items_list' %}" class="btn btn-outline-secondary">
|
|
{% translate "Annuler" %}
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock content %}
|
|
|
|
{% block script %}
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function() {
|
|
const productSelect = document.querySelector('select[name="product"]');
|
|
const locationSelect = document.querySelector('select[name="current_location"]');
|
|
const originalOptions = Array.from(locationSelect.options);
|
|
const apiBaseUrl = "{% url 'stock:product_locations_api' 0 %}".replace('/0/', '/{id}/');
|
|
|
|
function filterLocations() {
|
|
const productId = productSelect.value;
|
|
if (!productId) {
|
|
// If no product is selected, show all options
|
|
locationSelect.innerHTML = "";
|
|
originalOptions.forEach(opt => locationSelect.appendChild(opt.cloneNode(true)));
|
|
locationSelect.disabled = false;
|
|
return;
|
|
}
|
|
|
|
locationSelect.disabled = true;
|
|
const currentSelectedValue = locationSelect.value;
|
|
|
|
const url = apiBaseUrl.replace('{id}', productId);
|
|
fetch(url)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
const allowedIds = new Set(data.locations.map(loc => loc.id.toString()));
|
|
|
|
// Rebuild options: keep the "-- Aucun --" option (empty value)
|
|
// and only the allowed locations.
|
|
locationSelect.innerHTML = "";
|
|
originalOptions.forEach(opt => {
|
|
if (opt.value === "" || allowedIds.has(opt.value)) {
|
|
const newOpt = opt.cloneNode(true);
|
|
if (opt.value === currentSelectedValue) {
|
|
newOpt.selected = true;
|
|
}
|
|
locationSelect.appendChild(newOpt);
|
|
}
|
|
});
|
|
locationSelect.disabled = false;
|
|
})
|
|
.catch(error => {
|
|
console.error("Error fetching locations:", error);
|
|
locationSelect.disabled = false;
|
|
});
|
|
}
|
|
|
|
productSelect.addEventListener("change", filterLocations);
|
|
|
|
// Run on initial load if product is already selected (e.g. initial_product or after POST error)
|
|
if (productSelect.value) {
|
|
filterLocations();
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock script %}
|