feat: add views and templates for stock batch creation on desktop and mobile
This commit is contained in:
parent
8b13236a26
commit
ca4d1b3c1b
3 changed files with 51 additions and 11 deletions
|
|
@ -38,6 +38,16 @@
|
|||
{% endblock extra_head %}
|
||||
|
||||
{% block content %}
|
||||
{% if messages %}
|
||||
<div class="mb-3">
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{% if message.tags == 'error' %}danger{% elif message.tags %}{{ message.tags }}{% else %}info{% endif %} alert-dismissible fade show small" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
<form method="post" enctype="multipart/form-data" id="batch-form">
|
||||
{% csrf_token %}
|
||||
|
||||
|
|
@ -368,17 +378,20 @@ function initProductAutocomplete(lineEl) {
|
|||
? `<span class="badge bg-success ms-1">${p.total_stock}</span>`
|
||||
: `<span class="badge bg-secondary ms-1">0</span>`;
|
||||
item.innerHTML = `<strong>${escHtml(p.text)}</strong> <small class="text-muted">${escHtml(p.code || '')}</small>${stockBadge}`;
|
||||
item.addEventListener('mousedown', e => {
|
||||
const selectProduct = (e) => {
|
||||
e.preventDefault();
|
||||
hiddenInput.value = p.id;
|
||||
searchInput.value = p.text + (p.code ? ' (' + p.code + ')' : '');
|
||||
searchInput.classList.remove('is-invalid');
|
||||
resultsBox.style.display = 'none';
|
||||
if (p.is_serialized) {
|
||||
fetchSerializedItems(lineEl, p.id);
|
||||
} else {
|
||||
resetSerializedSection(lineEl);
|
||||
}
|
||||
});
|
||||
};
|
||||
item.addEventListener('pointerdown', selectProduct);
|
||||
item.addEventListener('mousedown', selectProduct);
|
||||
resultsBox.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
|
@ -389,7 +402,7 @@ function initProductAutocomplete(lineEl) {
|
|||
});
|
||||
|
||||
searchInput.addEventListener('blur', () => {
|
||||
setTimeout(() => { resultsBox.style.display = 'none'; }, 150);
|
||||
setTimeout(() => { resultsBox.style.display = 'none'; }, 250);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -694,7 +707,8 @@ function onCompanyInputChange() {
|
|||
// ── Filtrage des personnes par entreprise ────────────────────
|
||||
function filterPersonsByCompany(companyId) {
|
||||
const personSelect = document.getElementById('person_id');
|
||||
const currentVal = personSelect.value;
|
||||
if (!personSelect) return;
|
||||
const currentOpt = personSelect.selectedIndex >= 0 ? personSelect.options[personSelect.selectedIndex] : null;
|
||||
Array.from(personSelect.options).forEach(opt => {
|
||||
if (!opt.value) return; // keep the blank option always
|
||||
const allowedCompanies = (opt.dataset.companies || '').split(',');
|
||||
|
|
@ -751,13 +765,15 @@ if (bpSearchInput) {
|
|||
const item = document.createElement('div');
|
||||
item.className = 'list-group-item list-group-item-action py-2 small';
|
||||
item.innerHTML = `<strong>${escHtml(bp.text)}</strong> <span class="badge bg-secondary ms-1">${escHtml(bp.status)}</span>`;
|
||||
item.addEventListener('mousedown', e => {
|
||||
const selectBp = (e) => {
|
||||
e.preventDefault();
|
||||
bpHiddenId.value = bp.id;
|
||||
bpSearchInput.value = bp.text;
|
||||
bpResults.style.display = 'none';
|
||||
if (bpLoadBtn) bpLoadBtn.disabled = false;
|
||||
});
|
||||
};
|
||||
item.addEventListener('pointerdown', selectBp);
|
||||
item.addEventListener('mousedown', selectBp);
|
||||
bpResults.appendChild(item);
|
||||
});
|
||||
}
|
||||
|
|
@ -767,7 +783,7 @@ if (bpSearchInput) {
|
|||
}, 280);
|
||||
});
|
||||
bpSearchInput.addEventListener('blur', () => {
|
||||
setTimeout(() => { bpResults.style.display = 'none'; }, 150);
|
||||
setTimeout(() => { bpResults.style.display = 'none'; }, 250);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,17 @@
|
|||
</a>
|
||||
</div>
|
||||
|
||||
{% if messages %}
|
||||
<div class="mb-3">
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{% if message.tags == 'error' %}danger{% elif message.tags %}{{ message.tags }}{% else %}info{% endif %} alert-dismissible fade show small" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" enctype="multipart/form-data" id="batch-form">
|
||||
{% csrf_token %}
|
||||
|
||||
|
|
|
|||
|
|
@ -947,7 +947,13 @@ def _new_stock_batch_view(request, movement_type,
|
|||
return redirect(cancel_url_name)
|
||||
|
||||
products = Product.objects.select_related("category").order_by("name")
|
||||
locations = list(WarehouseLocation.objects.select_related("warehouse").order_by("warehouse__name", "name"))
|
||||
locations = list(
|
||||
filter_locations_by_permission(
|
||||
WarehouseLocation.objects.select_related("warehouse"),
|
||||
request.user,
|
||||
can_manage=True
|
||||
).order_by("warehouse__name", "name")
|
||||
)
|
||||
from stock.permissions import user_can_administer_warehouse
|
||||
for loc in locations:
|
||||
loc.can_administer = user_can_administer_warehouse(request.user, loc.warehouse)
|
||||
|
|
@ -1102,7 +1108,13 @@ def _new_stock_batch_view(request, movement_type,
|
|||
errors.append(_("Ligne %(n)s : produit invalide.") % {'n': i + 1})
|
||||
continue
|
||||
|
||||
lines.append({'product_id': pid, 'qty': qty, 'src': src, 'dst': dst})
|
||||
lines.append({
|
||||
'product_id': pid,
|
||||
'qty': qty,
|
||||
'src': src,
|
||||
'dst': dst,
|
||||
'line_index': i,
|
||||
})
|
||||
|
||||
if errors:
|
||||
for err in errors:
|
||||
|
|
@ -1153,9 +1165,10 @@ def _new_stock_batch_view(request, movement_type,
|
|||
reason=reason,
|
||||
batch=batch,
|
||||
)
|
||||
line_idx = line.get('line_index', 0)
|
||||
# Articles tracés (numéros de série)
|
||||
if product.is_serialized:
|
||||
ids_str = serialized_ids_list[i] if i < len(serialized_ids_list) else ''
|
||||
ids_str = serialized_ids_list[line_idx] if line_idx < len(serialized_ids_list) else ''
|
||||
s_ids = [int(x) for x in ids_str.split(',') if x.strip().isdigit()]
|
||||
s_items = list(SerializedItem.objects.filter(pk__in=s_ids, product=product)) if s_ids else []
|
||||
if s_items:
|
||||
|
|
@ -1165,7 +1178,7 @@ def _new_stock_batch_view(request, movement_type,
|
|||
)
|
||||
|
||||
# Nouveaux articles tracés (entrée uniquement)
|
||||
new_serials_str = serialized_new_serials_list[i] if i < len(serialized_new_serials_list) else ''
|
||||
new_serials_str = serialized_new_serials_list[line_idx] if line_idx < len(serialized_new_serials_list) else ''
|
||||
new_items = []
|
||||
if movement_type == 'in' and new_serials_str.strip():
|
||||
for sn in [s.strip() for s in new_serials_str.split(',') if s.strip()]:
|
||||
|
|
|
|||
Loading…
Reference in a new issue