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

@@ -131,7 +131,7 @@
</div>
<!-- Получатель -->
{% if not order.customer_is_recipient %}
{% if not order.customer_is_recipient and order.recipient %}
<div class="card mb-3">
<div class="card-header">
<h5 class="mb-0">Получатель</h5>
@@ -139,11 +139,11 @@
<div class="card-body">
<div class="row mb-2">
<div class="col-md-4"><strong>Имя получателя:</strong></div>
<div class="col-md-8">{{ order.recipient_name|default:"Не указано" }}</div>
<div class="col-md-8">{{ order.recipient.name|default:"Не указано" }}</div>
</div>
<div class="row mb-2">
<div class="col-md-4"><strong>Телефон получателя:</strong></div>
<div class="col-md-8">{{ order.recipient_phone|default:"Не указан" }}</div>
<div class="col-md-8">{{ order.recipient.phone|default:"Не указан" }}</div>
</div>
{% if order.is_anonymous %}
<div class="row mb-2">

View File

@@ -504,24 +504,34 @@
<div class="border-top pt-3 mt-3">
<h6 class="mb-3">Получатель</h6>
<!-- Крупный переключатель "Покупатель = получатель" -->
<!-- Режимы выбора получателя -->
<div class="mb-3">
<div class="form-check form-switch" style="padding-left: 3.5em;">
<input class="form-check-input" type="checkbox" role="switch"
id="{{ form.customer_is_recipient.id_for_label }}"
name="{{ form.customer_is_recipient.name }}"
{% if form.customer_is_recipient.value %}checked{% endif %}
style="width: 3em; height: 1.5em; cursor: pointer;">
<label class="form-check-label" for="{{ form.customer_is_recipient.id_for_label }}"
style="font-size: 1.1em; font-weight: 500; cursor: pointer; padding-left: 0.5em;">
<i class="bi bi-person-check-fill text-primary"></i>
Покупатель является получателем
{% for choice in form.recipient_mode %}
<div class="form-check">
{{ choice.tag }}
<label class="form-check-label" for="{{ choice.id_for_label }}">
{{ choice.choice_label }}
</label>
</div>
{% endfor %}
{% if form.recipient_mode.errors %}
<div class="text-danger">{{ form.recipient_mode.errors }}</div>
{% endif %}
</div>
<!-- Поля получателя (показываются когда покупатель != получатель) -->
<div class="row" id="recipient-fields" style="display: none;">
<!-- Выбор получателя из истории -->
<div class="mb-3" id="recipient-history-field" style="display: none;">
<label for="{{ form.recipient_from_history.id_for_label }}" class="form-label">
{{ form.recipient_from_history.label }}
</label>
{{ form.recipient_from_history }}
{% if form.recipient_from_history.errors %}
<div class="text-danger">{{ form.recipient_from_history.errors }}</div>
{% endif %}
</div>
<!-- Поля нового получателя -->
<div class="row" id="recipient-new-fields" style="display: none;">
<div class="col-md-6">
<div class="mb-3">
<label for="{{ form.recipient_name.id_for_label }}" class="form-label">
@@ -1461,18 +1471,32 @@ document.addEventListener('DOMContentLoaded', function() {
syncUIFromCheckbox();
// Показ/скрытие полей получателя
const customerIsRecipientCheckbox = document.getElementById('{{ form.customer_is_recipient.id_for_label }}');
const recipientFields = document.getElementById('recipient-fields');
const recipientModeRadios = document.querySelectorAll('input[name="recipient_mode"]');
const recipientHistoryField = document.getElementById('recipient-history-field');
const recipientNewFields = document.getElementById('recipient-new-fields');
function toggleRecipientFields() {
if (customerIsRecipientCheckbox.checked) {
recipientFields.style.display = 'none';
} else {
recipientFields.style.display = '';
const selectedMode = document.querySelector('input[name="recipient_mode"]:checked');
if (!selectedMode) return;
const mode = selectedMode.value;
// Скрываем все поля
recipientHistoryField.style.display = 'none';
recipientNewFields.style.display = 'none';
// Показываем нужные поля
if (mode === 'history') {
recipientHistoryField.style.display = 'block';
} else if (mode === 'new') {
recipientNewFields.style.display = 'block';
}
// Для 'customer' ничего не показываем
}
customerIsRecipientCheckbox.addEventListener('change', toggleRecipientFields);
recipientModeRadios.forEach(radio => {
radio.addEventListener('change', toggleRecipientFields);
});
toggleRecipientFields();
// === РАСЧЁТ ИТОГОВОЙ СУММЫ ТОВАРОВ ===