Изменения: 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>
96 lines
3.6 KiB
Python
96 lines
3.6 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
Сервис для расчета стоимости доставки.
|
||
Содержит расширяемую логику вычисления на основе различных условий.
|
||
"""
|
||
from decimal import Decimal
|
||
from typing import TYPE_CHECKING
|
||
|
||
if TYPE_CHECKING:
|
||
from orders.models import Order
|
||
|
||
|
||
class DeliveryCostCalculator:
|
||
"""
|
||
Калькулятор стоимости доставки.
|
||
Применяет различные правила для автоматического расчета.
|
||
"""
|
||
|
||
# Константы для правил расчета
|
||
FREE_DELIVERY_THRESHOLD = Decimal('100.00') # Бесплатная доставка от суммы
|
||
BASE_DELIVERY_COST = Decimal('15.00') # Базовая стоимость доставки
|
||
MIN_DELIVERY_COST = Decimal('0.00') # Минимальная стоимость
|
||
|
||
@classmethod
|
||
def calculate(cls, order: 'Order') -> Decimal:
|
||
"""
|
||
Рассчитывает стоимость доставки на основе условий заказа.
|
||
|
||
Args:
|
||
order: Заказ для расчета
|
||
|
||
Returns:
|
||
Decimal: Рассчитанная стоимость доставки
|
||
"""
|
||
# Самовывоз - доставка бесплатная
|
||
if not order.is_delivery:
|
||
return cls.MIN_DELIVERY_COST
|
||
|
||
# Рассчитываем сумму товаров
|
||
items_total = sum(
|
||
item.get_total_price()
|
||
for item in order.items.all()
|
||
)
|
||
|
||
# Применяем правила расчета
|
||
cost = cls._apply_calculation_rules(order, items_total)
|
||
|
||
return cost
|
||
|
||
@classmethod
|
||
def _apply_calculation_rules(cls, order: 'Order', items_total: Decimal) -> Decimal:
|
||
"""
|
||
Применяет правила расчета стоимости доставки.
|
||
Этот метод легко расширить для добавления новых правил.
|
||
|
||
Args:
|
||
order: Заказ
|
||
items_total: Сумма товаров в заказе
|
||
|
||
Returns:
|
||
Decimal: Стоимость доставки
|
||
"""
|
||
# Правило 1: Бесплатная доставка при заказе от определенной суммы
|
||
if items_total >= cls.FREE_DELIVERY_THRESHOLD:
|
||
return cls.MIN_DELIVERY_COST
|
||
|
||
# Правило 2: Базовая стоимость доставки
|
||
cost = cls.BASE_DELIVERY_COST
|
||
|
||
# Правило 3: Можно добавить расчет по адресу
|
||
# if order.delivery_address:
|
||
# cost += cls._calculate_distance_cost(order.delivery_address)
|
||
|
||
# Правило 4: Можно добавить надбавку за срочность
|
||
# if cls._is_urgent_delivery(order):
|
||
# cost *= Decimal('1.5')
|
||
|
||
return cost
|
||
|
||
@classmethod
|
||
def _calculate_distance_cost(cls, address) -> Decimal:
|
||
"""
|
||
Рассчитывает надбавку за расстояние.
|
||
Placeholder для будущей реализации с геокодингом.
|
||
"""
|
||
# TODO: Интеграция с картами для расчета расстояния
|
||
return Decimal('0.00')
|
||
|
||
@classmethod
|
||
def _is_urgent_delivery(cls, order: 'Order') -> bool:
|
||
"""
|
||
Проверяет, является ли доставка срочной.
|
||
"""
|
||
# TODO: Логика определения срочности
|
||
return False
|