Интегрирован компонент поиска товаров в документы списания с фильтром по складу
- Добавлен параметр warehouse в API search_products_and_variants - API фильтрует товары по наличию на указанном складе через Stock - Обновлен _apply_product_filters для поддержки warehouse_id - ProductSearchPicker теперь поддерживает data-warehouse-id - Warehouse автоматически передается в AJAX запросы - В WriteOffDocumentDetailView добавлены categories и tags в контекст - Компонент поиска встроен в detail.html с жестким фильтром по складу документа - Single-select режим для выбора одного товара - JS автоматически заполняет select формы при выборе товара - Отображение выбранного товара с фото и артикулом - Автофокус на поле количества после выбора товара - Пользователь видит только товары доступные на складе документа
This commit is contained in:
@@ -1,8 +1,12 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}Документ списания {{ document.document_number }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- CSS для компонента поиска -->
|
||||
<link rel="stylesheet" href="{% static 'products/css/product-search-picker.css' %}">
|
||||
|
||||
<div class="container-fluid px-4 py-3">
|
||||
<!-- Breadcrumbs -->
|
||||
<nav aria-label="breadcrumb" class="mb-2">
|
||||
@@ -162,12 +166,32 @@
|
||||
<!-- Боковая панель: добавление позиции -->
|
||||
{% if document.can_edit %}
|
||||
<div class="col-lg-4">
|
||||
<!-- Компонент поиска товаров -->
|
||||
<div class="mb-3">
|
||||
{% include 'products/components/product_search_picker.html' with container_id='writeoff-document-picker' title='Поиск товара для списания' warehouse_id=document.warehouse.id filter_in_stock_only=True categories=categories tags=tags add_button_text='Выбрать товар' multi_select=False show_select_all=False content_height='300px' %}
|
||||
</div>
|
||||
|
||||
<!-- Форма добавления позиции -->
|
||||
<div class="card border-0 shadow-sm sticky-top" style="top: 1rem;">
|
||||
<div class="card-header bg-light py-3">
|
||||
<h6 class="mb-0"><i class="bi bi-plus-circle me-2"></i>Добавить товар</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="{% url 'inventory:writeoff-document-add-item' document.pk %}">
|
||||
<!-- Информация о выбранном товаре -->
|
||||
<div id="selected-product-info" class="alert alert-info mb-3" style="display: none;">
|
||||
<div class="d-flex align-items-center">
|
||||
<img id="selected-product-photo" src="" alt="" class="rounded me-2" style="width: 40px; height: 40px; object-fit: cover; display: none;">
|
||||
<div class="flex-grow-1">
|
||||
<strong id="selected-product-name" class="d-block"></strong>
|
||||
<small class="text-muted" id="selected-product-sku"></small>
|
||||
</div>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" id="clear-selected-product" title="Очистить">
|
||||
<i class="bi bi-x"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'inventory:writeoff-document-add-item' document.pk %}" id="add-item-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -176,6 +200,9 @@
|
||||
{% if item_form.product.errors %}
|
||||
<div class="text-danger small">{{ item_form.product.errors.0 }}</div>
|
||||
{% endif %}
|
||||
<small class="text-muted d-block mt-1">
|
||||
<i class="bi bi-info-circle"></i> Используйте поиск выше для удобного выбора
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
@@ -206,4 +233,81 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JS для компонента поиска -->
|
||||
<script src="{% static 'products/js/product-search-picker.js' %}"></script>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Элементы формы
|
||||
const productSelect = document.querySelector('#id_product');
|
||||
const quantityInput = document.querySelector('#id_quantity');
|
||||
|
||||
// Элементы отображения выбранного товара
|
||||
const selectedProductInfo = document.getElementById('selected-product-info');
|
||||
const selectedProductName = document.getElementById('selected-product-name');
|
||||
const selectedProductSku = document.getElementById('selected-product-sku');
|
||||
const selectedProductPhoto = document.getElementById('selected-product-photo');
|
||||
const clearSelectedBtn = document.getElementById('clear-selected-product');
|
||||
|
||||
// Инициализация компонента поиска товаров
|
||||
const picker = ProductSearchPicker.init('#writeoff-document-picker', {
|
||||
onAddSelected: function(products, instance) {
|
||||
if (products.length > 0) {
|
||||
selectProduct(products[0]);
|
||||
instance.clearSelection();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Функция выбора товара
|
||||
function selectProduct(product) {
|
||||
const productId = String(product.id).replace('product_', '');
|
||||
const productName = product.text || product.name || '';
|
||||
|
||||
// Показываем информацию о выбранном товаре
|
||||
selectedProductName.textContent = productName;
|
||||
selectedProductSku.textContent = product.sku || '';
|
||||
|
||||
if (product.photo_url) {
|
||||
selectedProductPhoto.src = product.photo_url;
|
||||
selectedProductPhoto.style.display = 'block';
|
||||
} else {
|
||||
selectedProductPhoto.style.display = 'none';
|
||||
}
|
||||
|
||||
selectedProductInfo.style.display = 'block';
|
||||
|
||||
// Устанавливаем значение в select формы
|
||||
if (productSelect) {
|
||||
productSelect.value = productId;
|
||||
// Триггерим change для Select2, если он используется
|
||||
const event = new Event('change', { bubbles: true });
|
||||
productSelect.dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Фокус в поле количества
|
||||
if (quantityInput) {
|
||||
quantityInput.focus();
|
||||
quantityInput.select();
|
||||
}
|
||||
}
|
||||
|
||||
// Функция очистки выбора товара
|
||||
function clearSelectedProduct() {
|
||||
selectedProductInfo.style.display = 'none';
|
||||
selectedProductName.textContent = '';
|
||||
selectedProductSku.textContent = '';
|
||||
selectedProductPhoto.style.display = 'none';
|
||||
|
||||
if (productSelect) {
|
||||
productSelect.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Очистка выбора товара
|
||||
if (clearSelectedBtn) {
|
||||
clearSelectedBtn.addEventListener('click', clearSelectedProduct);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
@@ -60,6 +60,12 @@ class WriteOffDocumentDetailView(LoginRequiredMixin, DetailView):
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['item_form'] = WriteOffDocumentItemForm(document=self.object)
|
||||
|
||||
# Добавляем категории и теги для компонента поиска товаров
|
||||
from products.models import ProductCategory, ProductTag
|
||||
context['categories'] = ProductCategory.objects.filter(is_active=True).order_by('name')
|
||||
context['tags'] = ProductTag.objects.filter(is_active=True).order_by('name')
|
||||
|
||||
return context
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user