feat(orders): add recipient management and enhance order forms

- Introduced Recipient model to manage order recipients separately from customers.
- Updated Order model to link to Recipient, replacing recipient_name and recipient_phone fields.
- Enhanced OrderForm to include recipient selection modes: customer, history, and new.
- Added AJAX endpoint to fetch recipient history for customers.
- Updated admin interface to manage recipients and display recipient information in order details.
- Refactored address handling to accommodate new recipient logic.
- Improved demo order creation to include random recipients.
This commit is contained in:
2025-12-23 00:08:41 +03:00
parent 483f150e7a
commit 6669d47cdf
15 changed files with 559 additions and 110 deletions

View File

@@ -4,6 +4,7 @@
Структура:
- OrderStatus: Статусы заказов
- Address: Адреса доставки
- Recipient: Получатели заказов
- Order: Главная модель заказа
- OrderItem: Позиции в заказе
- PaymentMethod: Способы оплаты (справочник)
@@ -17,6 +18,7 @@ from .payment_method import PaymentMethod
# 2. Модели с зависимостями от справочников
from .address import Address
from .recipient import Recipient
# 3. Главная модель Order (зависит от Status, Address)
from .order import Order
@@ -29,6 +31,7 @@ from .transaction import Transaction
__all__ = [
'OrderStatus',
'Address',
'Recipient',
'Order',
'OrderItem',
'PaymentMethod',

View File

@@ -3,26 +3,9 @@ from django.db import models
class Address(models.Model):
"""
Модель адреса доставки для заказа цветочного магазина в Минске.
Адрес принадлежит конкретному заказу доставки.
Модель адреса доставки.
Адрес может использоваться в разных заказах и для разных получателей.
"""
# Информация о получателе
recipient_name = models.CharField(
max_length=200,
blank=True,
null=True,
verbose_name="Имя получателя",
help_text="Имя человека, которому будет доставлен заказ"
)
recipient_phone = models.CharField(
max_length=20,
blank=True,
null=True,
verbose_name="Телефон получателя",
help_text="Контактный телефон получателя для уточнения адреса"
)
street = models.CharField(
max_length=255,
blank=True,
@@ -103,12 +86,7 @@ class Address(models.Model):
if self.apartment_number:
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
return ", ".join(address_parts) if address_parts else "Адрес не указан"
@property
def full_address(self):

View File

@@ -6,6 +6,7 @@ from inventory.models import Warehouse
from simple_history.models import HistoricalRecords
from .status import OrderStatus
from .address import Address
from .recipient import Recipient
class Order(models.Model):
@@ -38,12 +39,12 @@ class Order(models.Model):
)
# Адрес доставки (для курьерской доставки)
delivery_address = models.OneToOneField(
delivery_address = models.ForeignKey(
Address,
on_delete=models.CASCADE,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='order',
related_name='orders',
verbose_name="Адрес доставки",
help_text="Обязательно для курьерской доставки"
)
@@ -160,30 +161,24 @@ class Order(models.Model):
help_text="Обновляется автоматически при добавлении платежей"
)
# Дополнительная информация
# Информация о получателе
customer_is_recipient = models.BooleanField(
default=True,
verbose_name="Покупатель является получателем",
help_text="Если отмечено, данные получателя не требуются отдельно"
)
# Данные получателя (если покупатель != получатель)
recipient_name = models.CharField(
max_length=200,
blank=True,
# Получатель (если покупатель != получатель)
recipient = models.ForeignKey(
Recipient,
on_delete=models.SET_NULL,
null=True,
verbose_name="Имя получателя",
blank=True,
related_name='orders',
verbose_name="Получатель",
help_text="Заполняется, если покупатель не является получателем"
)
recipient_phone = models.CharField(
max_length=20,
blank=True,
null=True,
verbose_name="Телефон получателя",
help_text="Контактный телефон получателя"
)
is_anonymous = models.BooleanField(
default=False,
verbose_name="Анонимная доставка",

View File

@@ -0,0 +1,41 @@
from django.db import models
class Recipient(models.Model):
"""
Модель получателя заказа.
Один получатель может получать доставки по разным адресам.
"""
name = models.CharField(
max_length=200,
verbose_name="Имя получателя",
help_text="ФИО или название организации получателя"
)
phone = models.CharField(
max_length=20,
verbose_name="Телефон получателя",
help_text="Контактный телефон для связи с получателем"
)
# Временные метки
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 = "Получатели"
indexes = [
models.Index(fields=['phone']),
models.Index(fields=['name']),
models.Index(fields=['created_at']),
]
ordering = ['-created_at']
def __str__(self):
return f"{self.name} ({self.phone})"
@property
def display_name(self):
"""Форматированное имя для отображения"""
return f"{self.name} - {self.phone}"