Files
octopus/myproject/orders/services/address_service.py
Andrey Smakotin 2f8a421e64 Улучшение модели Recipient: PhoneNumberField и поле notes
- Заменено поле phone с CharField на PhoneNumberField для автоматической нормализации телефонов
- Убран регион BY, установлен region=None для универсальности (поддержка номеров разных стран)
- Добавлено поле notes для дополнительной информации о получателе (мессенджеры, соцсети и т.д.)
- Улучшена логика поиска существующих получателей:
  * Использование нормализованного телефона из PhoneNumberField
  * Регистронезависимый поиск по имени (name__iexact)
  * Обновление notes при нахождении существующего получателя
- Обновлена форма OrderForm для работы с PhoneNumberField и новым полем notes
- Обновлен шаблон order_form.html для отображения нового поля
- Созданы миграции для изменений модели
2025-12-25 11:44:18 +03:00

250 lines
11 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Сервис для работы с адресами и получателями заказов.
Содержит логику создания, обновления и управления адресами доставки и получателями.
"""
from ..models import Order, Address, Recipient
class AddressService:
"""Сервис для управления адресами доставки и получателями в заказах"""
@staticmethod
def create_address_from_form_data(form_data):
"""
Создает объект Address из данных формы.
Args:
form_data (dict): Словарь с данными из формы
- address_street
- address_building_number
- address_apartment_number (опционально)
- address_entrance (опционально)
- address_floor (опционально)
- address_intercom_code (опционально)
- address_delivery_instructions (опционально)
Returns:
Address: Новый объект адреса (не сохраненный в БД)
"""
address = Address(
street=form_data.get('address_street', ''),
building_number=form_data.get('address_building_number', ''),
apartment_number=form_data.get('address_apartment_number', ''),
entrance=form_data.get('address_entrance', ''),
floor=form_data.get('address_floor', ''),
intercom_code=form_data.get('address_intercom_code', ''),
delivery_instructions=form_data.get('address_delivery_instructions', ''),
confirm_address_with_recipient=form_data.get('address_confirm_with_recipient', False),
)
return address
@staticmethod
def create_recipient_from_form_data(form_data):
"""
Создает объект Recipient из данных формы.
Args:
form_data (dict): Словарь с данными из формы
- recipient_name
- recipient_phone
- recipient_notes (опционально)
Returns:
Recipient: Новый объект получателя (не сохраненный в БД)
"""
recipient = Recipient(
name=form_data.get('recipient_name', ''),
phone=form_data.get('recipient_phone', ''),
notes=form_data.get('recipient_notes', '') or None,
)
return recipient
@staticmethod
def process_recipient_from_form(order, form_data):
"""
Обрабатывает получателя из данных формы заказа.
Создает нового Recipient или использует существующего в зависимости от режима.
Args:
order (Order): Объект заказа
form_data (dict): Все данные из формы
Returns:
Recipient or None: Получатель для привязки к заказу или None
"""
# Если чекбокс "Другой получатель" не включен - возвращаем None (получатель = покупатель)
other_recipient = form_data.get('other_recipient', False)
if not other_recipient:
return None
# Определяем источник получателя
recipient_source = form_data.get('recipient_source', 'new')
# Если режим "выбрать из истории" - возвращаем выбранного получателя
if recipient_source == 'history':
recipient_id = form_data.get('recipient_from_history')
if recipient_id:
try:
return Recipient.objects.get(pk=recipient_id)
except Recipient.DoesNotExist:
return None
# Если режим "новый получатель"
if recipient_source == 'new':
name = form_data.get('recipient_name', '').strip()
phone = form_data.get('recipient_phone', '')
notes = form_data.get('recipient_notes', '').strip() or None
if not name or not phone:
return None
# Если у заказа уже есть получатель - обновляем его вместо создания нового
# Загружаем заказ из БД, чтобы получить актуального получателя
if order.pk:
try:
# Order уже импортирован в начале файла
db_order = Order.objects.select_related('recipient').get(pk=order.pk)
if db_order.recipient:
# Обновляем существующего получателя
db_order.recipient.name = name
db_order.recipient.phone = phone
db_order.recipient.notes = notes
# Сохранять будем в views.py, здесь просто возвращаем объект
return db_order.recipient
except Order.DoesNotExist:
pass
# Проверяем, есть ли уже такой получатель в БД (по имени и нормализованному телефону)
# PhoneNumberField автоматически нормализует телефон, поэтому можно искать напрямую
existing_recipient = Recipient.objects.filter(
name__iexact=name.strip(),
phone=phone
).first()
if existing_recipient:
# Обновляем заметки, если они изменились
if existing_recipient.notes != notes:
existing_recipient.notes = notes
return existing_recipient
# Создаем нового получателя
recipient = AddressService.create_recipient_from_form_data(form_data)
return recipient
return None
@staticmethod
def process_address_from_form(order, form_data):
"""
Обрабатывает адрес из данных формы заказа.
Создает новый Address или использует существующий в зависимости от режима.
Args:
order (Order): Объект заказа
form_data (dict): Все данные из формы
Returns:
Address or None: Адрес для привязки к заказу или None
"""
address_mode = form_data.get('address_mode')
# Если режим "без адреса" - возвращаем None
if address_mode == 'empty':
return None
# Если режим "выбрать из истории" - возвращаем выбранный адрес
if address_mode == 'history':
address_id = form_data.get('address_from_history')
if address_id:
try:
return Address.objects.get(pk=address_id)
except Address.DoesNotExist:
return None
# Если режим "ввести новый адрес"
if address_mode == 'new':
# Проверяем обязательные поля
street = form_data.get('address_street', '').strip()
building_number = form_data.get('address_building_number', '').strip()
# Для автосохранения разрешаем сохранять адрес даже если не все поля заполнены
# Проверяем только что хотя бы одно поле адреса заполнено
has_any_address_data = any([
street,
building_number,
form_data.get('address_apartment_number', '').strip(),
form_data.get('address_entrance', '').strip(),
form_data.get('address_floor', '').strip(),
form_data.get('address_intercom_code', '').strip(),
form_data.get('address_delivery_instructions', '').strip(),
])
if not has_any_address_data:
# Если все поля адреса пустые, возвращаем None
return None
# Проверяем, есть ли уже такой адрес в БД (по основным полям)
existing_address = Address.objects.filter(
street=street,
building_number=building_number,
apartment_number=form_data.get('address_apartment_number', '').strip()
).first()
if existing_address:
return existing_address
# Создаем новый адрес
address = AddressService.create_address_from_form_data(form_data)
return address
return None
@staticmethod
def get_customer_address_history(customer):
"""
Получает список адресов из истории заказов клиента.
Args:
customer (Customer): Клиент
Returns:
QuerySet: Адреса, отсортированные по дате создания (новые первыми)
"""
customer_orders = Order.objects.filter(
customer=customer,
delivery_address__isnull=False
).order_by('-created_at')
addresses = Address.objects.filter(
orders__in=customer_orders
).distinct().order_by('-created_at')
return addresses
@staticmethod
def format_address_for_display(address):
"""
Форматирует адрес для отображения в списке.
Args:
address (Address): Объект адреса
Returns:
str: Форматированная строка адреса
"""
address_line = f"{address.street}, {address.building_number}"
if address.apartment_number:
address_line += f", кв. {address.apartment_number}"
details = []
if address.entrance:
details.append(f"подъезд {address.entrance}")
if address.floor:
details.append(f"этаж {address.floor}")
if details:
address_line += f" ({', '.join(details)})"
return address_line