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

279
test_variant_stock.py Normal file
View File

@@ -0,0 +1,279 @@
#!/usr/bin/env python
"""
Скрипт для тестирования системы наличия товаров и цен вариантов.
Проверяет:
1. Обновление Product.in_stock при создании Stock
2. Свойство ProductVariantGroup.in_stock (вариант в наличии если хотя бы один товар в наличии)
3. Свойство ProductVariantGroup.price (берётся цена по приоритету)
"""
import os
import sys
import django
# Добавляем путь к myproject
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'myproject'))
os.chdir(os.path.join(os.path.dirname(__file__), 'myproject'))
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django.setup()
from decimal import Decimal
from products.models import Product, ProductVariantGroup, ProductVariantGroupItem
from inventory.models import Stock, StockBatch, Warehouse, Incoming, IncomingBatch
from django.utils import timezone
def clear_data():
"""Очищаем тестовые данные"""
Product.objects.all().delete()
ProductVariantGroup.objects.all().delete()
Stock.objects.all().delete()
StockBatch.objects.all().delete()
Warehouse.objects.all().delete()
IncomingBatch.objects.all().delete()
print("OK Данные очищены")
def create_test_data():
"""Создаём тестовые данные"""
# Создаём склад
warehouse = Warehouse.objects.create(
name="Основной склад",
description="Тестовый склад",
is_active=True,
is_default=True
)
print(f"OK Создан склад: {warehouse.name}")
# Создаём товары (розы разной длины)
products = []
prices = [Decimal('50.00'), Decimal('60.00'), Decimal('70.00')]
for i, price in enumerate(prices, 1):
product = Product.objects.create(
name=f"Роза красная Freedom {50 + i*10}см",
sku=f"ROSE-RED-{50 + i*10}",
cost_price=Decimal('30.00'),
sale_price=price,
unit='шт',
is_active=True,
in_stock=False # По умолчанию нет в наличии
)
products.append(product)
print(f"OK Создан товар: {product.name} (цена: {price}, in_stock={product.in_stock})")
# Создаём группу вариантов
variant_group = ProductVariantGroup.objects.create(
name="Роза красная Freedom",
description="Розы разной высоты"
)
print(f"OK Создана группа вариантов: {variant_group.name}")
# Добавляем товары в группу с приоритетами
items = []
for priority, product in enumerate(products, 1):
item = ProductVariantGroupItem.objects.create(
variant_group=variant_group,
product=product,
priority=priority
)
items.append(item)
print(f" - {product.name} (приоритет {priority})")
return warehouse, products, variant_group, items
def test_scenario_1():
"""
Тест 1: Создание товара в наличии и проверка автоматического обновления in_stock
"""
print("\n" + "="*80)
print("ТЕСТ 1: Обновление Product.in_stock при создании Stock")
print("="*80)
warehouse, products, variant_group, items = create_test_data()
# Получаем первый товар
product = products[0]
print(f"\nПроверяем товар: {product.name}")
print(f"Текущий статус in_stock: {product.in_stock}")
# Создаём приход товара (это должно создать Stock и обновить in_stock)
incoming_batch = IncomingBatch.objects.create(
warehouse=warehouse,
document_number="IN-0001",
supplier_name="Тестовый поставщик"
)
print(f"\nOK Создана партия поступления: {incoming_batch.document_number}")
# Добавляем товар в приход
incoming = Incoming.objects.create(
batch=incoming_batch,
product=product,
quantity=Decimal('100.00'),
cost_price=product.cost_price
)
print(f"OK Добавлен товар в приход: {incoming.quantity} шт")
# Проверяем что Stock был создан и in_stock обновлён
stock = Stock.objects.get(product=product, warehouse=warehouse)
print(f"\nOK Stock создан:")
print(f" - quantity_available: {stock.quantity_available}")
print(f" - quantity_reserved: {stock.quantity_reserved}")
print(f" - quantity_free: {stock.quantity_free}")
# Обновляем товар из БД чтобы получить новое значение
product.refresh_from_db()
print(f"\nOK Product.in_stock обновлён: {product.in_stock}")
if product.in_stock:
print("PASS: ТЕСТ 1 ПРОЙДЕН: Product.in_stock = True")
else:
print("FAIL: ТЕСТ 1 ПРОВАЛЕН: Product.in_stock должен быть True")
return warehouse, products, variant_group, items
def test_scenario_2(warehouse, products, variant_group, items):
"""
Тест 2: Проверка свойства ProductVariantGroup.in_stock
"""
print("\n" + "="*80)
print("ТЕСТ 2: Свойство ProductVariantGroup.in_stock")
print("="*80)
# Обновляем товары из БД
for product in products:
product.refresh_from_db()
print(f"\nГруппа вариантов: {variant_group.name}")
print(f"Товары в группе:")
for item in variant_group.items.all():
print(f" - {item.product.name} (приоритет {item.priority}, in_stock={item.product.in_stock})")
# Первый товар в наличии, поэтому вариант должен быть в наличии
print(f"\nСвойство variant_group.in_stock: {variant_group.in_stock}")
if variant_group.in_stock:
print("PASS: ТЕСТ 2 ПРОЙДЕН: Вариант в наличии (хотя бы один товар доступен)")
else:
print("FAIL: ТЕСТ 2 ПРОВАЛЕН: Вариант должен быть в наличии")
def test_scenario_3(warehouse, products, variant_group, items):
"""
Тест 3: Проверка свойства ProductVariantGroup.price
"""
print("\n" + "="*80)
print("ТЕСТ 3: Свойство ProductVariantGroup.price")
print("="*80)
print(f"\nГруппа вариантов: {variant_group.name}")
# Обновляем товары из БД
for product in products:
product.refresh_from_db()
print(f"Товары в приоритете:")
for item in variant_group.items.all().order_by('priority'):
status = "OK В наличии" if item.product.in_stock else "NO Нет в наличии"
print(f" {item.priority}. {item.product.name} - {item.product.sale_price} руб {status}")
# Цена должна быть из первого в наличии (приоритет 1, цена 50.00)
price = variant_group.price
expected_price = Decimal('50.00')
print(f"\nЦена варианта: {price} руб")
print(f"Ожидаемая цена: {expected_price} руб")
if price == expected_price:
print("PASS: ТЕСТ 3 ПРОЙДЕН: Берётся цена товара с приоритетом 1")
else:
print(f"FAIL: ТЕСТ 3 ПРОВАЛЕН: Цена должна быть {expected_price}, получена {price}")
def test_scenario_4():
"""
Тест 4: Проверка цены когда нет товара в наличии (должна быть максимальная)
"""
print("\n" + "="*80)
print("ТЕСТ 4: Цена варианта когда ни один товар не в наличии")
print("="*80)
# Очищаем данные
clear_data()
# Создаём новые данные без Stock (товары не в наличии)
warehouse = Warehouse.objects.create(
name="Тестовый склад",
is_active=True,
is_default=True
)
# Создаём товары с разными ценами
products = []
prices = [Decimal('100.00'), Decimal('150.00'), Decimal('200.00')]
for i, price in enumerate(prices, 1):
product = Product.objects.create(
name=f"Товар {i}",
sku=f"PRODUCT-{i}",
cost_price=Decimal('50.00'),
sale_price=price,
unit='шт',
is_active=True,
in_stock=False # Нет в наличии
)
products.append(product)
# Создаём группу вариантов
variant_group = ProductVariantGroup.objects.create(
name="Группа товаров без наличия"
)
# Добавляем товары в группу
for priority, product in enumerate(products, 1):
ProductVariantGroupItem.objects.create(
variant_group=variant_group,
product=product,
priority=priority
)
print(f"\nГруппа: {variant_group.name}")
print(f"Товары (все без наличия):")
for item in variant_group.items.all().order_by('priority'):
print(f" {item.priority}. {item.product.name} - {item.product.sale_price} руб (in_stock={item.product.in_stock})")
# Цена должна быть максимальная = 200.00
price = variant_group.price
expected_price = Decimal('200.00')
print(f"\nЦена варианта (максимальная): {price} руб")
print(f"Ожидаемая цена (максимальная): {expected_price} руб")
if price == expected_price:
print("PASS: ТЕСТ 4 ПРОЙДЕН: Берётся максимальная цена из товаров")
else:
print(f"FAIL: ТЕСТ 4 ПРОВАЛЕН: Цена должна быть {expected_price}, получена {price}")
if __name__ == '__main__':
print("\n" + "="*80)
print("= ТЕСТИРОВАНИЕ СИСТЕМЫ НАЛИЧИЯ ТОВАРОВ И ЦЕН ВАРИАНТОВ")
print("="*80)
try:
# Очищаем старые данные
clear_data()
# Тесты 1-3
warehouse, products, variant_group, items = test_scenario_1()
test_scenario_2(warehouse, products, variant_group, items)
test_scenario_3(warehouse, products, variant_group, items)
# Тест 4
test_scenario_4()
print("\n" + "="*80)
print("= ТЕСТИРОВАНИЕ ЗАВЕРШЕНО")
print("="*80 + "\n")
except Exception as e:
print(f"\nОШИБКА: {e}")
import traceback
traceback.print_exc()