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:
3
myproject/products/validators/__init__.py
Normal file
3
myproject/products/validators/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
"""
|
||||
Валидаторы для products приложения.
|
||||
"""
|
||||
147
myproject/products/validators/kit_validators.py
Normal file
147
myproject/products/validators/kit_validators.py
Normal file
@@ -0,0 +1,147 @@
|
||||
"""
|
||||
Валидаторы для ProductKit модели.
|
||||
Извлекает логику валидации из метода clean().
|
||||
"""
|
||||
from decimal import Decimal
|
||||
from django.core.exceptions import ValidationError
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class KitValidator:
|
||||
"""
|
||||
Валидатор для проверки корректности данных ProductKit.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def validate_pricing_method(kit):
|
||||
"""
|
||||
Проверяет соответствие метода ценообразования заданным полям.
|
||||
|
||||
Args:
|
||||
kit (ProductKit): Комплект для валидации
|
||||
|
||||
Raises:
|
||||
ValidationError: Если данные не соответствуют выбранному методу ценообразования
|
||||
"""
|
||||
# Проверка соответствия метода ценообразования полям
|
||||
if kit.pricing_method == 'manual' and not kit.price:
|
||||
raise ValidationError({
|
||||
'price': 'Для метода ценообразования "Ручная цена" необходимо указать цену.'
|
||||
})
|
||||
|
||||
if kit.pricing_method == 'from_cost_plus_percent' and (
|
||||
kit.markup_percent is None or kit.markup_percent < 0
|
||||
):
|
||||
raise ValidationError({
|
||||
'markup_percent': 'Для метода ценообразования "from_cost_plus_percent" необходимо указать процент наценки >= 0.'
|
||||
})
|
||||
|
||||
if kit.pricing_method == 'from_cost_plus_amount' and (
|
||||
kit.markup_amount is None or kit.markup_amount < 0
|
||||
):
|
||||
raise ValidationError({
|
||||
'markup_amount': 'Для метода ценообразования "from_cost_plus_amount" необходимо указать сумму наценки >= 0.'
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def validate_sku_uniqueness(kit):
|
||||
"""
|
||||
Проверяет уникальность SKU комплекта.
|
||||
|
||||
Args:
|
||||
kit (ProductKit): Комплект для валидации
|
||||
|
||||
Raises:
|
||||
ValidationError: Если SKU уже используется другим комплектом
|
||||
"""
|
||||
if not kit.sku:
|
||||
return
|
||||
|
||||
# Импортируем здесь, чтобы избежать циклических зависимостей
|
||||
from ..models.kits import ProductKit
|
||||
|
||||
# Проверяем, что SKU не используется другим комплектом (если объект уже существует)
|
||||
if kit.pk:
|
||||
if ProductKit.objects.filter(sku=kit.sku).exclude(pk=kit.pk).exists():
|
||||
raise ValidationError({
|
||||
'sku': f'Артикул "{kit.sku}" уже используется другим комплектом.'
|
||||
})
|
||||
else:
|
||||
# Для новых объектов просто проверяем, что SKU не используется
|
||||
if ProductKit.objects.filter(sku=kit.sku).exists():
|
||||
raise ValidationError({
|
||||
'sku': f'Артикул "{kit.sku}" уже используется другим комплектом.'
|
||||
})
|
||||
|
||||
@staticmethod
|
||||
def validate_pricing_method_availability(kit):
|
||||
"""
|
||||
Проверяет, доступны ли методы ценообразования на основе данных себестоимости.
|
||||
|
||||
Если себестоимость компонентов неполная, блокирует методы:
|
||||
- 'from_cost_plus_percent'
|
||||
- 'from_cost_plus_amount'
|
||||
|
||||
И переключает на 'from_sale_prices' с предупреждением.
|
||||
|
||||
Args:
|
||||
kit (ProductKit): Комплект для валидации
|
||||
|
||||
Returns:
|
||||
tuple: (is_valid: bool, message: str or None)
|
||||
- is_valid: True если метод ценообразования доступен, False если был переключен
|
||||
- message: Сообщение об изменении метода ценообразования
|
||||
"""
|
||||
# Методы, требующие валидной себестоимости
|
||||
restricted_methods = ['from_cost_plus_percent', 'from_cost_plus_amount']
|
||||
|
||||
# Проверяем валидность себестоимости
|
||||
cost_info = kit.cost_calculation_info
|
||||
|
||||
# Если себестоимость не валидна и выбран ограниченный метод
|
||||
if not cost_info['is_valid'] and kit.pricing_method in restricted_methods:
|
||||
# Переключаемся на 'from_sale_prices'
|
||||
old_method = kit.pricing_method
|
||||
kit.pricing_method = 'from_sale_prices'
|
||||
|
||||
# Формируем сообщение об ошибке
|
||||
problems_text = ', '.join([
|
||||
f"{p['component_name']} — {p['reason']}"
|
||||
for p in cost_info['problems']
|
||||
])
|
||||
|
||||
message = (
|
||||
f"⚠️ Метод ценообразования был переключен с '{KitValidator._get_method_label(old_method)}' "
|
||||
f"на 'По ценам компонентов', так как не все компоненты имеют полную информацию о себестоимости. "
|
||||
f"Проблемы: {problems_text}"
|
||||
)
|
||||
|
||||
logger.info(
|
||||
f"Kit {kit.name} (id={kit.pk}): pricing_method переключен с {old_method} "
|
||||
f"на from_sale_prices из-за неполной себестоимости"
|
||||
)
|
||||
|
||||
return False, message
|
||||
|
||||
return True, None
|
||||
|
||||
@staticmethod
|
||||
def _get_method_label(method_code):
|
||||
"""
|
||||
Получить человеческое описание метода ценообразования.
|
||||
|
||||
Args:
|
||||
method_code (str): Код метода ценообразования
|
||||
|
||||
Returns:
|
||||
str: Описание метода
|
||||
"""
|
||||
method_labels = {
|
||||
'manual': 'Ручная цена',
|
||||
'from_sale_prices': 'По ценам компонентов',
|
||||
'from_cost_plus_percent': 'Себестоимость + процент',
|
||||
'from_cost_plus_amount': 'Себестоимость + фиксированная наценка'
|
||||
}
|
||||
return method_labels.get(method_code, method_code)
|
||||
Reference in New Issue
Block a user