- Изменена проверка с >= на > в Delivery.clean() - Равные времена разрешены для POS-продаж (самовывоз в точное время) - Обновлены сообщения об ошибках валидации
166 lines
6.4 KiB
Python
166 lines
6.4 KiB
Python
# -*- coding: utf-8 -*-
|
||
from django.db import models
|
||
from django.core.exceptions import ValidationError
|
||
|
||
|
||
class Delivery(models.Model):
|
||
"""
|
||
Модель доставки заказа.
|
||
Один заказ имеет одну доставку.
|
||
"""
|
||
|
||
# Константы для типов доставки
|
||
DELIVERY_TYPE_COURIER = 'courier'
|
||
DELIVERY_TYPE_PICKUP = 'pickup'
|
||
|
||
DELIVERY_TYPE_CHOICES = [
|
||
(DELIVERY_TYPE_COURIER, 'Доставка курьером'),
|
||
(DELIVERY_TYPE_PICKUP, 'Самовывоз'),
|
||
]
|
||
|
||
# === Связи ===
|
||
|
||
order = models.OneToOneField(
|
||
'orders.Order',
|
||
on_delete=models.CASCADE,
|
||
related_name='delivery',
|
||
verbose_name='Заказ',
|
||
help_text='Заказ, к которому относится доставка'
|
||
)
|
||
|
||
# Адрес доставки (только для курьерской доставки)
|
||
address = models.ForeignKey(
|
||
'orders.Address',
|
||
on_delete=models.SET_NULL,
|
||
null=True,
|
||
blank=True,
|
||
related_name='deliveries',
|
||
verbose_name='Адрес доставки',
|
||
help_text='Адрес для курьерской доставки. На один адрес может быть много доставок'
|
||
)
|
||
|
||
# Склад для самовывоза (только для самовывоза)
|
||
pickup_warehouse = models.ForeignKey(
|
||
'inventory.Warehouse',
|
||
on_delete=models.PROTECT,
|
||
null=True,
|
||
blank=True,
|
||
related_name='deliveries',
|
||
verbose_name='Склад самовывоза',
|
||
help_text='Склад для самовывоза заказа'
|
||
)
|
||
|
||
# === Основные поля ===
|
||
|
||
delivery_type = models.CharField(
|
||
max_length=20,
|
||
choices=DELIVERY_TYPE_CHOICES,
|
||
default=DELIVERY_TYPE_COURIER,
|
||
verbose_name='Способ доставки',
|
||
db_index=True
|
||
)
|
||
|
||
# Дата и время доставки
|
||
delivery_date = models.DateField(
|
||
verbose_name='Дата доставки',
|
||
help_text='Дата, когда должна быть выполнена доставка'
|
||
)
|
||
|
||
time_from = models.TimeField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name='Время доставки от',
|
||
help_text='Начальное время временного интервала доставки (необязательно)'
|
||
)
|
||
|
||
time_to = models.TimeField(
|
||
null=True,
|
||
blank=True,
|
||
verbose_name='Время доставки до',
|
||
help_text='Конечное время временного интервала доставки (необязательно)'
|
||
)
|
||
|
||
cost = models.DecimalField(
|
||
max_digits=10,
|
||
decimal_places=2,
|
||
default=0,
|
||
verbose_name='Стоимость доставки',
|
||
help_text='Стоимость доставки в рублях. 0 для бесплатной доставки/самовывоза'
|
||
)
|
||
|
||
# === Метаданные ===
|
||
|
||
created_at = models.DateTimeField(
|
||
auto_now_add=True,
|
||
verbose_name='Дата создания'
|
||
)
|
||
|
||
updated_at = models.DateTimeField(
|
||
auto_now=True,
|
||
verbose_name='Дата обновления'
|
||
)
|
||
|
||
class Meta:
|
||
verbose_name = 'Доставка'
|
||
verbose_name_plural = 'Доставки'
|
||
ordering = ['-created_at']
|
||
indexes = [
|
||
models.Index(fields=['delivery_type']),
|
||
models.Index(fields=['created_at']),
|
||
models.Index(fields=['delivery_date']),
|
||
models.Index(fields=['time_from']),
|
||
models.Index(fields=['time_to']),
|
||
]
|
||
|
||
def __str__(self):
|
||
"""Строковое представление доставки"""
|
||
type_display = self.get_delivery_type_display()
|
||
return f"{type_display} для заказа #{self.order.order_number}"
|
||
|
||
def clean(self):
|
||
"""Валидация модели"""
|
||
super().clean()
|
||
|
||
# Для черновиков пропускаем строгую валидацию
|
||
if self.order and self.order.status and hasattr(self.order.status, 'code') and self.order.status.code == 'draft':
|
||
# Для черновиков только проверяем время, если оно указано
|
||
if self.time_from and self.time_to and self.time_from > self.time_to:
|
||
raise ValidationError({
|
||
'time_to': 'Время окончания доставки не может быть раньше времени начала'
|
||
})
|
||
return
|
||
|
||
# Для не-черновиков полная валидация
|
||
# Проверка: для курьерской доставки должен быть адрес
|
||
if self.delivery_type == self.DELIVERY_TYPE_COURIER:
|
||
if not self.address:
|
||
raise ValidationError({
|
||
'address': 'Для курьерской доставки необходимо указать адрес'
|
||
})
|
||
if self.pickup_warehouse:
|
||
raise ValidationError({
|
||
'pickup_warehouse': 'Для курьерской доставки склад не указывается'
|
||
})
|
||
|
||
# Проверка: для самовывоза должен быть склад
|
||
if self.delivery_type == self.DELIVERY_TYPE_PICKUP:
|
||
if not self.pickup_warehouse:
|
||
raise ValidationError({
|
||
'pickup_warehouse': 'Для самовывоза необходимо указать склад'
|
||
})
|
||
if self.address:
|
||
raise ValidationError({
|
||
'address': 'Для самовывоза адрес не указывается'
|
||
})
|
||
|
||
# Проверка: время "до" не может быть раньше времени "от" (равные времена разрешены для POS)
|
||
if self.time_from and self.time_to and self.time_from > self.time_to:
|
||
raise ValidationError({
|
||
'time_to': 'Время окончания доставки не может быть раньше времени начала'
|
||
})
|
||
|
||
def save(self, *args, **kwargs):
|
||
"""Переопределение save для вызова валидации"""
|
||
self.full_clean()
|
||
super().save(*args, **kwargs)
|