Упрощение системы получателей доставки
- Удалено избыточное поле customer_is_recipient из модели Order - Добавлено свойство @property is_customer_recipient для обратной совместимости - Заменены радиокнопки recipient_mode на чекбокс 'Другой получатель' в форме - Добавлено поле recipient_source для выбора между историей и новым получателем - Обновлен AddressService.process_recipient_from_form() для работы с чекбоксом - Обновлены шаблоны: order_form.html (чекбокс вместо радиокнопок) и order_detail.html - Удалено customer_is_recipient из admin и demo команды - Создана миграция для удаления поля customer_is_recipient Логика упрощена: recipient is None = получатель = покупатель, иначе - отдельный получатель
This commit is contained in:
@@ -86,9 +86,9 @@ class OrderAdmin(admin.ModelAdmin):
|
|||||||
}),
|
}),
|
||||||
('Получатель', {
|
('Получатель', {
|
||||||
'fields': (
|
'fields': (
|
||||||
'customer_is_recipient',
|
|
||||||
'recipient',
|
'recipient',
|
||||||
)
|
),
|
||||||
|
'description': 'Если получатель не указан, получателем является покупатель'
|
||||||
}),
|
}),
|
||||||
('Оплата', {
|
('Оплата', {
|
||||||
'fields': (
|
'fields': (
|
||||||
|
|||||||
@@ -11,16 +11,24 @@ class OrderForm(forms.ModelForm):
|
|||||||
"""Форма для создания и редактирования заказа"""
|
"""Форма для создания и редактирования заказа"""
|
||||||
|
|
||||||
# Поля для работы с получателем
|
# Поля для работы с получателем
|
||||||
recipient_mode = forms.ChoiceField(
|
other_recipient = forms.BooleanField(
|
||||||
|
required=False,
|
||||||
|
initial=False,
|
||||||
|
widget=forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||||
|
label='Другой получатель',
|
||||||
|
help_text='Если отмечено, получатель отличается от покупателя'
|
||||||
|
)
|
||||||
|
|
||||||
|
# Режим выбора другого получателя (история или новый)
|
||||||
|
recipient_source = forms.ChoiceField(
|
||||||
choices=[
|
choices=[
|
||||||
('customer', 'Покупатель является получателем'),
|
|
||||||
('history', 'Выбрать из истории'),
|
('history', 'Выбрать из истории'),
|
||||||
('new', 'Другой получатель'),
|
('new', 'Новый получатель'),
|
||||||
],
|
],
|
||||||
initial='customer',
|
initial='new',
|
||||||
widget=forms.RadioSelect(attrs={'class': 'form-check-input'}),
|
widget=forms.RadioSelect(attrs={'class': 'form-check-input'}),
|
||||||
required=False,
|
required=False,
|
||||||
label='Получатель'
|
label='Способ указания получателя'
|
||||||
)
|
)
|
||||||
|
|
||||||
# Выбор получателя из истории
|
# Выбор получателя из истории
|
||||||
@@ -170,7 +178,6 @@ class OrderForm(forms.ModelForm):
|
|||||||
model = Order
|
model = Order
|
||||||
fields = [
|
fields = [
|
||||||
'customer',
|
'customer',
|
||||||
'customer_is_recipient',
|
|
||||||
'recipient',
|
'recipient',
|
||||||
'status',
|
'status',
|
||||||
'is_anonymous',
|
'is_anonymous',
|
||||||
@@ -232,28 +239,47 @@ class OrderForm(forms.ModelForm):
|
|||||||
'data-placeholder': 'Начните вводить имя, телефон или email'
|
'data-placeholder': 'Начните вводить имя, телефон или email'
|
||||||
})
|
})
|
||||||
|
|
||||||
# Подсказки
|
|
||||||
self.fields['customer_is_recipient'].label = 'Покупатель = получатель'
|
|
||||||
|
|
||||||
# Поле получателя опционально
|
# Поле получателя опционально
|
||||||
self.fields['recipient'].required = False
|
self.fields['recipient'].required = False
|
||||||
|
|
||||||
|
# Инициализируем чекбокс other_recipient на основе наличия recipient
|
||||||
|
if self.instance.pk:
|
||||||
|
# При редактировании заказа: если есть recipient, чекбокс включен
|
||||||
|
if self.instance.recipient:
|
||||||
|
self.fields['other_recipient'].initial = True
|
||||||
|
# Определяем источник получателя
|
||||||
|
# Проверяем, есть ли этот recipient в истории клиента
|
||||||
|
if self.instance.customer:
|
||||||
|
customer_orders = Order.objects.filter(
|
||||||
|
customer=self.instance.customer,
|
||||||
|
recipient__isnull=False
|
||||||
|
).exclude(pk=self.instance.pk).order_by('-created_at')
|
||||||
|
history_recipients = Recipient.objects.filter(
|
||||||
|
orders__in=customer_orders
|
||||||
|
).distinct()
|
||||||
|
if self.instance.recipient in history_recipients:
|
||||||
|
self.fields['recipient_source'].initial = 'history'
|
||||||
|
self.fields['recipient_from_history'].initial = self.instance.recipient.pk
|
||||||
|
else:
|
||||||
|
self.fields['recipient_source'].initial = 'new'
|
||||||
|
self.fields['recipient_name'].initial = self.instance.recipient.name or ''
|
||||||
|
self.fields['recipient_phone'].initial = self.instance.recipient.phone or ''
|
||||||
|
else:
|
||||||
|
self.fields['other_recipient'].initial = False
|
||||||
|
|
||||||
# Инициализируем queryset для recipient_from_history
|
# Инициализируем queryset для recipient_from_history
|
||||||
if self.instance.pk and self.instance.customer:
|
if self.instance.pk and self.instance.customer:
|
||||||
# При редактировании заказа загружаем историю получателей этого клиента
|
# При редактировании заказа загружаем историю получателей этого клиента
|
||||||
customer_orders = Order.objects.filter(
|
customer_orders = Order.objects.filter(
|
||||||
customer=self.instance.customer,
|
customer=self.instance.customer,
|
||||||
recipient__isnull=False
|
recipient__isnull=False
|
||||||
).order_by('-created_at')
|
).exclude(pk=self.instance.pk).order_by('-created_at')
|
||||||
self.fields['recipient_from_history'].queryset = Recipient.objects.filter(
|
self.fields['recipient_from_history'].queryset = Recipient.objects.filter(
|
||||||
orders__in=customer_orders
|
orders__in=customer_orders
|
||||||
).distinct().order_by('-created_at')
|
).distinct().order_by('-created_at')
|
||||||
|
elif not self.instance.pk:
|
||||||
# Инициализируем поля получателя из существующего recipient
|
# При создании нового заказа queryset пустой (будет заполнен через JS при выборе клиента)
|
||||||
if self.instance.pk and self.instance.recipient:
|
self.fields['recipient_from_history'].queryset = Recipient.objects.none()
|
||||||
recipient = self.instance.recipient
|
|
||||||
self.fields['recipient_name'].initial = recipient.name or ''
|
|
||||||
self.fields['recipient_phone'].initial = recipient.phone or ''
|
|
||||||
|
|
||||||
# Инициализируем queryset для pickup_warehouse
|
# Инициализируем queryset для pickup_warehouse
|
||||||
from inventory.models import Warehouse
|
from inventory.models import Warehouse
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ class Command(BaseCommand):
|
|||||||
|
|
||||||
# Дополнительная информация
|
# Дополнительная информация
|
||||||
if random.random() > 0.7: # 30% - подарок другому человеку
|
if random.random() > 0.7: # 30% - подарок другому человеку
|
||||||
order.customer_is_recipient = False
|
|
||||||
# Создаем получателя
|
# Создаем получателя
|
||||||
recipient_name = f"Получатель {i+1}"
|
recipient_name = f"Получатель {i+1}"
|
||||||
recipient_phone = f"+7{random.randint(9000000000, 9999999999)}"
|
recipient_phone = f"+7{random.randint(9000000000, 9999999999)}"
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2025-12-24 10:51
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('orders', '0002_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='historicalorder',
|
||||||
|
name='customer_is_recipient',
|
||||||
|
),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='order',
|
||||||
|
name='customer_is_recipient',
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -94,13 +94,7 @@ class Order(models.Model):
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Информация о получателе
|
# Информация о получателе
|
||||||
customer_is_recipient = models.BooleanField(
|
# Получатель (если None - получатель = покупатель, иначе - отдельный получатель)
|
||||||
default=True,
|
|
||||||
verbose_name="Покупатель является получателем",
|
|
||||||
help_text="Если отмечено, данные получателя не требуются отдельно"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Получатель (если покупатель != получатель)
|
|
||||||
recipient = models.ForeignKey(
|
recipient = models.ForeignKey(
|
||||||
Recipient,
|
Recipient,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
@@ -324,3 +318,8 @@ class Order(models.Model):
|
|||||||
if hasattr(self, 'delivery') and self.delivery:
|
if hasattr(self, 'delivery') and self.delivery:
|
||||||
return self.delivery.pickup_warehouse
|
return self.delivery.pickup_warehouse
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_customer_recipient(self):
|
||||||
|
"""Является ли покупатель получателем (обратная совместимость)"""
|
||||||
|
return self.recipient is None
|
||||||
|
|||||||
@@ -38,4 +38,4 @@ class Recipient(models.Model):
|
|||||||
@property
|
@property
|
||||||
def display_name(self):
|
def display_name(self):
|
||||||
"""Форматированное имя для отображения"""
|
"""Форматированное имя для отображения"""
|
||||||
return f"{self.name} - {self.phone}"
|
return f"Получатель{self.name} - {self.phone}"
|
||||||
|
|||||||
@@ -70,14 +70,16 @@ class AddressService:
|
|||||||
Returns:
|
Returns:
|
||||||
Recipient or None: Получатель для привязки к заказу или None
|
Recipient or None: Получатель для привязки к заказу или None
|
||||||
"""
|
"""
|
||||||
recipient_mode = form_data.get('recipient_mode')
|
# Если чекбокс "Другой получатель" не включен - возвращаем None (получатель = покупатель)
|
||||||
|
other_recipient = form_data.get('other_recipient', False)
|
||||||
# Если режим "покупатель = получатель" - возвращаем None
|
if not other_recipient:
|
||||||
if recipient_mode == 'customer':
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# Определяем источник получателя
|
||||||
|
recipient_source = form_data.get('recipient_source', 'new')
|
||||||
|
|
||||||
# Если режим "выбрать из истории" - возвращаем выбранного получателя
|
# Если режим "выбрать из истории" - возвращаем выбранного получателя
|
||||||
if recipient_mode == 'history':
|
if recipient_source == 'history':
|
||||||
recipient_id = form_data.get('recipient_from_history')
|
recipient_id = form_data.get('recipient_from_history')
|
||||||
if recipient_id:
|
if recipient_id:
|
||||||
try:
|
try:
|
||||||
@@ -86,7 +88,7 @@ class AddressService:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
# Если режим "новый получатель"
|
# Если режим "новый получатель"
|
||||||
if recipient_mode == 'new':
|
if recipient_source == 'new':
|
||||||
name = form_data.get('recipient_name', '').strip()
|
name = form_data.get('recipient_name', '').strip()
|
||||||
phone = form_data.get('recipient_phone', '').strip()
|
phone = form_data.get('recipient_phone', '').strip()
|
||||||
|
|
||||||
|
|||||||
@@ -131,7 +131,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Получатель -->
|
<!-- Получатель -->
|
||||||
{% if not order.customer_is_recipient and order.recipient %}
|
{% if order.recipient %}
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h5 class="mb-0">Получатель</h5>
|
<h5 class="mb-0">Получатель</h5>
|
||||||
|
|||||||
@@ -532,9 +532,28 @@
|
|||||||
<div class="border-top pt-3 mt-3">
|
<div class="border-top pt-3 mt-3">
|
||||||
<h6 class="mb-3">Получатель</h6>
|
<h6 class="mb-3">Получатель</h6>
|
||||||
|
|
||||||
<!-- Режимы выбора получателя -->
|
<!-- Чекбокс "Другой получатель" -->
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
{% for choice in form.recipient_mode %}
|
<div class="form-check">
|
||||||
|
{{ form.other_recipient }}
|
||||||
|
<label class="form-check-label" for="{{ form.other_recipient.id_for_label }}">
|
||||||
|
{{ form.other_recipient.label }}
|
||||||
|
</label>
|
||||||
|
{% if form.other_recipient.help_text %}
|
||||||
|
<small class="form-text text-muted">{{ form.other_recipient.help_text }}</small>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if form.other_recipient.errors %}
|
||||||
|
<div class="text-danger">{{ form.other_recipient.errors }}</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Блок для другого получателя (показывается при включенном чекбоксе) -->
|
||||||
|
<div id="other-recipient-block" style="display: none;">
|
||||||
|
<!-- Режим выбора получателя (история или новый) -->
|
||||||
|
<div class="mb-3">
|
||||||
|
<label class="form-label d-block mb-2">{{ form.recipient_source.label }}</label>
|
||||||
|
{% for choice in form.recipient_source %}
|
||||||
<div class="form-check">
|
<div class="form-check">
|
||||||
{{ choice.tag }}
|
{{ choice.tag }}
|
||||||
<label class="form-check-label" for="{{ choice.id_for_label }}">
|
<label class="form-check-label" for="{{ choice.id_for_label }}">
|
||||||
@@ -542,8 +561,8 @@
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% if form.recipient_mode.errors %}
|
{% if form.recipient_source.errors %}
|
||||||
<div class="text-danger">{{ form.recipient_mode.errors }}</div>
|
<div class="text-danger">{{ form.recipient_source.errors }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -584,6 +603,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Стоимость доставки -->
|
<!-- Стоимость доставки -->
|
||||||
<div class="row mt-3">
|
<div class="row mt-3">
|
||||||
@@ -971,31 +991,50 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
syncUIFromCheckbox();
|
syncUIFromCheckbox();
|
||||||
|
|
||||||
// Показ/скрытие полей получателя
|
// Показ/скрытие полей получателя
|
||||||
const recipientModeRadios = document.querySelectorAll('input[name="recipient_mode"]');
|
const otherRecipientCheckbox = document.getElementById('{{ form.other_recipient.id_for_label }}');
|
||||||
|
const otherRecipientBlock = document.getElementById('other-recipient-block');
|
||||||
|
const recipientSourceRadios = document.querySelectorAll('input[name="recipient_source"]');
|
||||||
const recipientHistoryField = document.getElementById('recipient-history-field');
|
const recipientHistoryField = document.getElementById('recipient-history-field');
|
||||||
const recipientNewFields = document.getElementById('recipient-new-fields');
|
const recipientNewFields = document.getElementById('recipient-new-fields');
|
||||||
|
|
||||||
function toggleRecipientFields() {
|
function toggleOtherRecipientBlock() {
|
||||||
const selectedMode = document.querySelector('input[name="recipient_mode"]:checked');
|
if (otherRecipientCheckbox.checked) {
|
||||||
if (!selectedMode) return;
|
otherRecipientBlock.style.display = 'block';
|
||||||
|
toggleRecipientSourceFields();
|
||||||
|
} else {
|
||||||
|
otherRecipientBlock.style.display = 'none';
|
||||||
|
recipientHistoryField.style.display = 'none';
|
||||||
|
recipientNewFields.style.display = 'none';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const mode = selectedMode.value;
|
function toggleRecipientSourceFields() {
|
||||||
|
const selectedSource = document.querySelector('input[name="recipient_source"]:checked');
|
||||||
|
if (!selectedSource) return;
|
||||||
|
|
||||||
|
const source = selectedSource.value;
|
||||||
|
|
||||||
// Скрываем все поля
|
// Скрываем все поля
|
||||||
recipientHistoryField.style.display = 'none';
|
recipientHistoryField.style.display = 'none';
|
||||||
recipientNewFields.style.display = 'none';
|
recipientNewFields.style.display = 'none';
|
||||||
|
|
||||||
// Показываем нужные поля
|
// Показываем нужные поля
|
||||||
if (mode === 'history') {
|
if (source === 'history') {
|
||||||
recipientHistoryField.style.display = 'block';
|
recipientHistoryField.style.display = 'block';
|
||||||
} else if (mode === 'new') {
|
} else if (source === 'new') {
|
||||||
recipientNewFields.style.display = 'block';
|
recipientNewFields.style.display = 'block';
|
||||||
}
|
}
|
||||||
// Для 'customer' ничего не показываем
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recipientModeRadios.forEach(radio => {
|
// Обработчики событий
|
||||||
radio.addEventListener('change', toggleRecipientFields);
|
if (otherRecipientCheckbox) {
|
||||||
|
otherRecipientCheckbox.addEventListener('change', toggleOtherRecipientBlock);
|
||||||
|
// Инициализация при загрузке
|
||||||
|
toggleOtherRecipientBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
recipientSourceRadios.forEach(radio => {
|
||||||
|
radio.addEventListener('change', toggleRecipientSourceFields);
|
||||||
});
|
});
|
||||||
toggleRecipientFields();
|
toggleRecipientFields();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user