Реализована система управления стоимостью доставки и исправлен баг выбора клиента

Изменения:

1. **Стоимость доставки (автоматическая/ручная)**:
   - Добавлено поле `is_custom_delivery_cost` в модель Order для отслеживания ручной стоимости
   - Создан сервис DeliveryCostCalculator для автоматического расчета стоимости доставки
   - Реализована логика: бесплатная доставка от 100 руб., иначе 15 руб.
   - В форме поле "Ручная стоимость доставки" - если заполнено, используется ручная стоимость, если пустое - автоматический расчет
   - Добавлены методы Order.get_delivery_cost(), set_delivery_cost(), reset_delivery_cost(), recalculate_delivery_cost()

2. **Исправление бага выбора клиента в Select2**:
   - Удален избыточный обработчик события select2:opening, который блокировал клики
   - Добавлены проверки на наличие e.params.data в обработчиках select2:selecting и select2:select
   - Теперь выбор клиента работает корректно, создается черновик заказа и происходит редирект

3. **Опциональные поля адреса**:
   - Поля recipient_name, street, building_number в модели Address сделаны необязательными (blank=True, null=True)
   - Обновлены методы __str__ и full_address для безопасной работы с None значениями

4. **UI улучшения**:
   - Удалена звездочка обязательности с полей адреса
   - Добавлена подсказка под полем ручной стоимости доставки о правилах автоматического расчета

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-11 15:48:50 +03:00
parent 6c207e9451
commit 885ac839e2
5 changed files with 351 additions and 60 deletions

View File

@@ -15,6 +15,8 @@ class Address(models.Model):
# Информация о получателе
recipient_name = models.CharField(
max_length=200,
blank=True,
null=True,
verbose_name="Имя получателя",
help_text="Имя человека, которому будет доставлен заказ"
)
@@ -29,11 +31,15 @@ class Address(models.Model):
street = models.CharField(
max_length=255,
blank=True,
null=True,
verbose_name="Улица"
)
building_number = models.CharField(
max_length=20,
blank=True,
null=True,
verbose_name="Номер здания"
)
@@ -94,17 +100,43 @@ class Address(models.Model):
ordering = ['-created_at']
def __str__(self):
address_line = f"{self.street}, {self.building_number}"
# Собираем компоненты адреса
address_parts = []
if self.street:
address_parts.append(self.street)
if self.building_number:
address_parts.append(self.building_number)
if self.apartment_number:
address_line += f", кв/офис {self.apartment_number}"
return f"{self.recipient_name} - {address_line}"
address_parts.append(f"кв/офис {self.apartment_number}")
address_line = ", ".join(address_parts) if address_parts else "Адрес не указан"
# Формируем строку с именем получателя
if self.recipient_name:
return f"{self.recipient_name} - {address_line}"
return address_line
@property
def full_address(self):
"""Полный адрес для доставки"""
address = f"{self.street}, {self.building_number}"
# Собираем основные компоненты адреса
address_parts = []
if self.street:
address_parts.append(self.street)
if self.building_number:
address_parts.append(self.building_number)
# Если нет основных данных, возвращаем сообщение
if not address_parts:
return "Адрес не указан"
address = ", ".join(address_parts)
# Добавляем квартиру/офис
if self.apartment_number:
address += f", кв/офис {self.apartment_number}"
# Собираем дополнительные детали
details = []
if self.entrance:
details.append(f"подъезд {self.entrance}")
@@ -112,6 +144,7 @@ class Address(models.Model):
details.append(f"этаж {self.floor}")
if details:
address += f" ({', '.join(details)})"
return address
@@ -193,6 +226,12 @@ class Order(models.Model):
help_text="0 для самовывоза"
)
is_custom_delivery_cost = models.BooleanField(
default=False,
verbose_name="Стоимость доставки установлена вручную",
help_text="True если стоимость доставки была изменена вручную"
)
# Статус заказа
STATUS_CHOICES = [
('draft', 'Черновик'),
@@ -351,6 +390,7 @@ class Order(models.Model):
models.Index(fields=['payment_status']),
models.Index(fields=['created_at']),
models.Index(fields=['order_number']),
models.Index(fields=['is_custom_delivery_cost']),
]
ordering = ['-created_at']
@@ -387,9 +427,56 @@ class Order(models.Model):
'delivery_time_end': 'Время окончания должно быть позже времени начала'
})
def get_delivery_cost(self):
"""
Возвращает стоимость доставки:
- Если установлена вручную - использует ручное значение
- Если автоматическая - вычисляет на основе правил
Returns:
Decimal: Стоимость доставки
"""
if self.is_custom_delivery_cost:
return self.delivery_cost
else:
from orders.services.delivery_cost_calculator import DeliveryCostCalculator
return DeliveryCostCalculator.calculate(self)
def set_delivery_cost(self, cost, is_custom=True):
"""
Устанавливает стоимость доставки.
Args:
cost: Новая стоимость доставки (Decimal)
is_custom: True если устанавливается вручную, False если автоматически
"""
self.delivery_cost = cost
self.is_custom_delivery_cost = is_custom
def reset_delivery_cost(self):
"""
Сбрасывает стоимость доставки на автоматический расчет.
"""
from orders.services.delivery_cost_calculator import DeliveryCostCalculator
self.delivery_cost = DeliveryCostCalculator.calculate(self)
self.is_custom_delivery_cost = False
def recalculate_delivery_cost(self):
"""
Пересчитывает стоимость доставки, если она не установлена вручную.
Используется при изменении параметров заказа (товаров, адреса и т.д.)
"""
if not self.is_custom_delivery_cost:
from orders.services.delivery_cost_calculator import DeliveryCostCalculator
self.delivery_cost = DeliveryCostCalculator.calculate(self)
def calculate_total(self):
"""Рассчитывает итоговую сумму заказа"""
items_total = sum(item.get_total_price() for item in self.items.all())
# Пересчитываем стоимость доставки если она автоматическая
self.recalculate_delivery_cost()
subtotal = items_total + self.delivery_cost
self.total_amount = subtotal - self.discount_amount
return self.total_amount
@@ -416,6 +503,16 @@ class Order(models.Model):
"""Остаток к оплате"""
return max(self.total_amount - self.amount_paid, 0)
@property
def delivery_cost_display(self):
"""
Возвращает строку для отображения стоимости доставки с пометкой.
Полезно в админке и шаблонах.
"""
cost = self.get_delivery_cost()
suffix = " (ручная)" if self.is_custom_delivery_cost else " (авто)"
return f"{cost} руб.{suffix}"
@property
def delivery_info(self):
"""Информация о доставке для отображения"""