fix: Улучшения системы ценообразования комплектов

Исправлены 4 проблемы:
1. Расчёт цены первого товара - улучшена валидация в getProductPrice и calculateFinalPrice
2. Отображение actual_price в Select2 вместо обычной цены
3. Количество по умолчанию = 1 для новых форм компонентов
4. Auto-select текста при клике на поле количества для удобства редактирования

Изменённые файлы:
- products/forms.py: добавлен __init__ в KitItemForm для quantity.initial = 1
- products/templates/includes/select2-product-init.html: обновлена formatSelectResult
- products/templates/productkit_create.html: добавлен focus handler для auto-select
- products/templates/productkit_edit.html: добавлен focus handler для auto-select

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-02 19:04:03 +03:00
parent c84a372f98
commit 6c8af5ab2c
120 changed files with 9035 additions and 3036 deletions

View File

@@ -1,4 +1,5 @@
<!-- КОМПОНЕНТЫ КОМПЛЕКТА - Shared include для создания и редактирования -->
{% load inventory_filters %}
<div class="card border-0 shadow-sm mb-3">
<div class="card-body p-3">
<h6 class="mb-3 text-muted"><i class="bi bi-boxes me-1"></i>Состав комплекта</h6>
@@ -7,7 +8,9 @@
<div id="kititem-forms">
{% for kititem_form in kititem_formset %}
<div class="card mb-2 kititem-form border" data-form-index="{{ forloop.counter0 }}">
<div class="card mb-2 kititem-form border"
data-form-index="{{ forloop.counter0 }}"
data-product-price="{% if kititem_form.instance.product %}{{ kititem_form.instance.product.actual_price|default:0 }}{% else %}0{% endif %}">
{{ kititem_form.id }}
<div class="card-body p-2">
{% if kititem_form.non_field_errors %}
@@ -17,13 +20,27 @@
{% endif %}
<div class="row g-2 align-items-end">
<div class="col-md-5">
<!-- ТОВАР -->
<div class="col-md-4">
<label class="form-label small text-muted mb-1">Товар</label>
{{ kititem_form.product }}
{% if kititem_form.product.errors %}
<div class="text-danger small">{{ kititem_form.product.errors }}</div>
{% endif %}
</div>
<!-- РАЗДЕЛИТЕЛЬ ИЛИ -->
<div class="col-md-1 d-flex justify-content-center align-items-center">
<div class="kit-item-separator">
<span class="separator-text">ИЛИ</span>
<i class="bi bi-info-circle separator-help"
data-bs-toggle="tooltip"
data-bs-placement="top"
title="Вы можете выбрать что-то одно: либо товар, либо группу вариантов"></i>
</div>
</div>
<!-- ГРУППА ВАРИАНТОВ -->
<div class="col-md-4">
<label class="form-label small text-muted mb-1">Группа вариантов</label>
{{ kititem_form.variant_group }}
@@ -31,13 +48,17 @@
<div class="text-danger small">{{ kititem_form.variant_group.errors }}</div>
{% endif %}
</div>
<!-- КОЛИЧЕСТВО -->
<div class="col-md-2">
<label class="form-label small text-muted mb-1">Кол-во</label>
{{ kititem_form.quantity }}
{{ kititem_form.quantity|smart_quantity }}
{% if kititem_form.quantity.errors %}
<div class="text-danger small">{{ kititem_form.quantity.errors }}</div>
{% endif %}
</div>
<!-- УДАЛЕНИЕ -->
<div class="col-md-1 text-end">
{% if kititem_form.DELETE %}
<button type="button" class="btn btn-sm btn-link text-danger p-0" onclick="this.previousElementSibling.checked = true; this.closest('.kititem-form').style.display='none';" title="Удалить">

View File

@@ -0,0 +1,90 @@
<!-- Select2 Product Search Initialization -->
<!-- Используется для инициализации Select2 с AJAX поиском товаров -->
<!-- Требует: jQuery, Select2 CSS/JS, и переменные: apiUrl, containerSelector, fieldNamePattern -->
<script>
(function() {
// Функции форматирования для Select2
function formatSelectResult(item) {
if (item.loading) return item.text;
var $container = $('<div class="select2-result-item">');
$container.text(item.text);
// Отображаем actual_price (цену со скидкой если она есть), иначе обычную цену
var displayPrice = item.actual_price || item.price;
if (displayPrice) {
$container.append($('<div class="text-muted small">').text(displayPrice + ' руб.'));
}
return $container;
}
function formatSelectSelection(item) {
if (!item.id) return item.text;
// Показываем только текст при выборе, цена будет обновляться в JavaScript
return item.text || item.id;
}
/**
* Инициализирует Select2 для элемента с AJAX поиском товаров
* @param {Element} element - DOM элемент select
* @param {string} type - Тип поиска ('product' или 'variant')
* @param {string} apiUrl - URL API для поиска
*/
window.initProductSelect2 = function(element, type, apiUrl) {
if (!element || $(element).data('select2')) {
return; // Уже инициализирован
}
var placeholders = {
'product': 'Начните вводить название товара...',
'variant': 'Начните вводить название группы...'
};
$(element).select2({
theme: 'bootstrap-5',
placeholder: placeholders[type] || 'Выберите...',
allowClear: true,
width: '100%',
language: 'ru',
minimumInputLength: 0,
dropdownAutoWidth: false,
ajax: {
url: apiUrl,
dataType: 'json',
delay: 250,
data: function (params) {
return {
q: params.term || '',
type: type,
page: params.page || 1
};
},
processResults: function (data) {
return {
results: data.results,
pagination: {
more: data.pagination.more
}
};
},
cache: true
},
templateResult: formatSelectResult,
templateSelection: formatSelectSelection
});
};
/**
* Инициализирует Select2 для всех селектов, совпадающих с паттерном
* @param {string} fieldPattern - Паттерн name атрибута (например: 'items-', 'kititem-')
* @param {string} type - Тип поиска ('product' или 'variant')
* @param {string} apiUrl - URL API для поиска
*/
window.initAllProductSelect2 = function(fieldPattern, type, apiUrl) {
document.querySelectorAll('[name*="' + fieldPattern + '"][name*="-product"]').forEach(function(element) {
window.initProductSelect2(element, type, apiUrl);
});
};
})();
</script>