Консолидация миграций и добавление unit_service

- Обновлены начальные миграции для всех приложений
- Удалены устаревшие миграции для единиц измерения и SKU
- Добавлен новый сервис unit_service.py для управления единицами
- Обновлены команды инициализации данных тенанта
This commit is contained in:
2026-01-03 12:09:31 +03:00
parent 030d5ad198
commit 208c6b55de
22 changed files with 272 additions and 355 deletions

View File

@@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
"""
Сервис для управления единицами измерения.
Отвечает за создание и управление справочником единиц измерения (UnitOfMeasure).
"""
import logging
from typing import List, Dict, Any
logger = logging.getLogger(__name__)
class UnitOfMeasureService:
"""
Сервис для управления единицами измерения.
Предоставляет методы для создания и управления базовыми единицами измерения,
которые используются в системе для товаров и продаж.
"""
# Базовый набор единиц измерения для новых тенантов
DEFAULT_UNITS = [
# Базовые единицы (из старых UNIT_CHOICES)
{'code': 'шт', 'name': 'Штука', 'short_name': 'шт.', 'position': 1},
{'code': 'м', 'name': 'Метр', 'short_name': 'м.', 'position': 2},
{'code': 'г', 'name': 'Грамм', 'short_name': 'г.', 'position': 3},
{'code': 'л', 'name': 'Литр', 'short_name': 'л.', 'position': 4},
{'code': 'кг', 'name': 'Килограмм', 'short_name': 'кг.', 'position': 5},
# Флористические единицы
{'code': 'банч', 'name': 'Банч', 'short_name': 'банч', 'position': 10},
{'code': 'ветка', 'name': 'Ветка', 'short_name': 'вет.', 'position': 11},
{'code': 'пучок', 'name': 'Пучок', 'short_name': 'пуч.', 'position': 12},
]
@classmethod
def create_default_units(cls) -> List:
"""
Создает базовый набор единиц измерения для тенанта.
Использует get_or_create, поэтому безопасно вызывать повторно.
Returns:
List[UnitOfMeasure]: Список созданных/существующих единиц измерения
"""
from products.models import UnitOfMeasure
created_units = []
for unit_data in cls.DEFAULT_UNITS:
unit, created = UnitOfMeasure.objects.get_or_create(
code=unit_data['code'],
defaults={
'name': unit_data['name'],
'short_name': unit_data['short_name'],
'position': unit_data['position'],
'is_active': True,
}
)
created_units.append(unit)
if created:
logger.debug(f"Создана единица измерения: {unit.code} - {unit.name}")
logger.info(f"Инициализация единиц измерения завершена: {len(created_units)} единиц")
return created_units
@classmethod
def reset_default_units(cls) -> List:
"""
Удаляет все единицы измерения и создаёт их заново.
ВНИМАНИЕ: Используйте только при инициализации тенанта или в тестах!
Удаление единиц может нарушить связи с существующими товарами.
Returns:
List[UnitOfMeasure]: Список созданных единиц измерения
"""
from products.models import UnitOfMeasure
logger.warning("Удаление всех единиц измерения...")
UnitOfMeasure.objects.all().delete()
return cls.create_default_units()
@classmethod
def get_or_create_unit(cls, code: str, name: str, short_name: str,
position: int = 0) -> tuple:
"""
Получает или создаёт единицу измерения.
Args:
code: Уникальный код единицы
name: Полное название
short_name: Короткое название для UI
position: Позиция для сортировки
Returns:
tuple: (UnitOfMeasure, created) - единица и флаг создания
"""
from products.models import UnitOfMeasure
unit, created = UnitOfMeasure.objects.get_or_create(
code=code,
defaults={
'name': name,
'short_name': short_name,
'position': position,
'is_active': True,
}
)
if created:
logger.info(f"Создана единица измерения: {code} - {name}")
return unit, created
@classmethod
def get_unit_by_code(cls, code: str):
"""
Получает единицу измерения по коду.
Args:
code: Код единицы измерения
Returns:
UnitOfMeasure или None, если не найдена
"""
from products.models import UnitOfMeasure
try:
return UnitOfMeasure.objects.get(code=code, is_active=True)
except UnitOfMeasure.DoesNotExist:
logger.warning(f"Единица измерения с кодом '{code}' не найдена")
return None
@classmethod
def get_active_units(cls) -> List:
"""
Возвращает все активные единицы измерения.
Returns:
List[UnitOfMeasure]: Список активных единиц, отсортированных по position
"""
from products.models import UnitOfMeasure
return list(UnitOfMeasure.objects.filter(is_active=True).order_by('position', 'code'))