Рефакторинг: убрана финализация черновиков и улучшены шаблоны заказов
- Убран черновик как отдельная сущность с процессом финализации - Черновик теперь просто обычный OrderStatus - Удалены кнопки 'Сохранить как черновик' и 'Финализировать черновик' - Унифицирована логика сохранения/обновления заказов для всех статусов Улучшения шаблонов: - Стандартизировано форматирование валюты через floatformat:2 - Исправлено отображение статуса (используется OrderStatus.label и color) - Исправлено отображение способа оплаты (корректное использование ForeignKey) - Добавлены иконки к заголовкам секций для лучшего UX - Удалены избыточные console.log (~160 строк) - Очищены комментарии и улучшена читаемость кода - Убрано использование переменной is_draft в контексте - Добавлена визуальная согласованность между шаблонами заказов
This commit is contained in:
@@ -139,6 +139,46 @@ class WalletService:
|
|||||||
|
|
||||||
return usable_amount
|
return usable_amount
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
@transaction.atomic
|
||||||
|
def refund_wallet_payment(order, amount, user):
|
||||||
|
"""
|
||||||
|
Возврат средств в кошелёк при удалении платежа.
|
||||||
|
Увеличивает баланс кошелька и создаёт транзакцию deposit.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
order: Заказ, по которому был платёж
|
||||||
|
amount: Сумма возврата
|
||||||
|
user: Пользователь, инициировавший возврат
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Decimal: Возвращённая сумма
|
||||||
|
"""
|
||||||
|
from customers.models import Customer, WalletTransaction
|
||||||
|
|
||||||
|
amount = _quantize(amount)
|
||||||
|
if amount <= 0:
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Блокируем запись клиента
|
||||||
|
customer = Customer.objects.select_for_update().get(pk=order.customer_id)
|
||||||
|
|
||||||
|
# Увеличиваем баланс
|
||||||
|
customer.wallet_balance = _quantize(customer.wallet_balance + amount)
|
||||||
|
customer.save(update_fields=['wallet_balance'])
|
||||||
|
|
||||||
|
# Создаём транзакцию возврата
|
||||||
|
WalletTransaction.objects.create(
|
||||||
|
customer=customer,
|
||||||
|
amount=amount,
|
||||||
|
transaction_type='deposit',
|
||||||
|
order=order,
|
||||||
|
description=f'Возврат платежа по заказу #{order.order_number}',
|
||||||
|
created_by=user
|
||||||
|
)
|
||||||
|
|
||||||
|
return amount
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def adjust_balance(customer_id, amount, description, user):
|
def adjust_balance(customer_id, amount, description, user):
|
||||||
|
|||||||
@@ -45,18 +45,12 @@
|
|||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-md-4"><strong>Статус:</strong></div>
|
<div class="col-md-4"><strong>Статус:</strong></div>
|
||||||
<div class="col-md-8">
|
<div class="col-md-8">
|
||||||
{% if order.status == 'new' %}
|
{% if order.status %}
|
||||||
<span class="badge bg-primary">Новый</span>
|
<span class="badge" style="background-color: {{ order.status.color }}; color: #fff;">
|
||||||
{% elif order.status == 'confirmed' %}
|
{{ order.status.label|default:order.status.name }}
|
||||||
<span class="badge bg-success">Подтвержден</span>
|
</span>
|
||||||
{% elif order.status == 'in_assembly' %}
|
{% else %}
|
||||||
<span class="badge bg-warning">В сборке</span>
|
<span class="badge bg-secondary">Не установлен</span>
|
||||||
{% elif order.status == 'in_delivery' %}
|
|
||||||
<span class="badge bg-info">В доставке</span>
|
|
||||||
{% elif order.status == 'delivered' %}
|
|
||||||
<span class="badge bg-success">Доставлен</span>
|
|
||||||
{% elif order.status == 'cancelled' %}
|
|
||||||
<span class="badge bg-danger">Отменен</span>
|
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -126,7 +120,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-md-4"><strong>Стоимость доставки:</strong></div>
|
<div class="col-md-4"><strong>Стоимость доставки:</strong></div>
|
||||||
<div class="col-md-8">{{ order.delivery_cost }} руб.</div>
|
<div class="col-md-8">{{ order.delivery_cost|floatformat:2 }} руб.</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
@@ -207,25 +201,25 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>{{ item.quantity }}</td>
|
<td>{{ item.quantity }} шт.</td>
|
||||||
<td>
|
<td>
|
||||||
{{ item.price }} руб.
|
{{ item.price|floatformat:2 }} руб.
|
||||||
{% if item.is_custom_price %}
|
{% if item.is_custom_price %}
|
||||||
<span class="badge bg-warning ms-1">Изменена</span>
|
<span class="badge bg-warning ms-1">Изменена</span>
|
||||||
<br>
|
<br>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
Оригинальная: {{ item.original_price }} руб.
|
Оригинальная: {{ item.original_price|floatformat:2 }} руб.
|
||||||
{% if item.price_difference %}
|
{% if item.price_difference %}
|
||||||
{% if item.price_difference > 0 %}
|
{% if item.price_difference > 0 %}
|
||||||
<span class="text-success">(+{{ item.price_difference }} руб.)</span>
|
<span class="text-success">(+{{ item.price_difference|floatformat:2 }} руб.)</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
<span class="text-danger">({{ item.price_difference }} руб.)</span>
|
<span class="text-danger">({{ item.price_difference|floatformat:2 }} руб.)</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</small>
|
</small>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td><strong>{{ item.get_total_price }} руб.</strong></td>
|
<td><strong>{{ item.get_total_price|floatformat:2 }} руб.</strong></td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
@@ -315,27 +309,27 @@
|
|||||||
{% if order.is_delivery %}
|
{% if order.is_delivery %}
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-6"><strong>Доставка:</strong></div>
|
<div class="col-6"><strong>Доставка:</strong></div>
|
||||||
<div class="col-6 text-end">{{ order.delivery_cost }} руб.</div>
|
<div class="col-6 text-end">{{ order.delivery_cost|floatformat:2 }} руб.</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if order.discount_amount > 0 %}
|
{% if order.discount_amount > 0 %}
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-6"><strong>Скидка:</strong></div>
|
<div class="col-6"><strong>Скидка:</strong></div>
|
||||||
<div class="col-6 text-end text-danger">-{{ order.discount_amount }} руб.</div>
|
<div class="col-6 text-end text-danger">-{{ order.discount_amount|floatformat:2 }} руб.</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<hr>
|
<hr>
|
||||||
<div class="row mb-3">
|
<div class="row mb-3">
|
||||||
<div class="col-6"><strong>Итого:</strong></div>
|
<div class="col-6"><strong>Итого:</strong></div>
|
||||||
<div class="col-6 text-end"><h5>{{ order.total_amount }} руб.</h5></div>
|
<div class="col-6 text-end"><h5>{{ order.total_amount|floatformat:2 }} руб.</h5></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-6"><strong>Оплачено:</strong></div>
|
<div class="col-6"><strong>Оплачено:</strong></div>
|
||||||
<div class="col-6 text-end">{{ order.amount_paid }} руб.</div>
|
<div class="col-6 text-end">{{ order.amount_paid|floatformat:2 }} руб.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-6"><strong>К оплате:</strong></div>
|
<div class="col-6"><strong>К оплате:</strong></div>
|
||||||
<div class="col-6 text-end text-danger"><strong>{{ order.amount_due }} руб.</strong></div>
|
<div class="col-6 text-end text-danger"><strong>{{ order.amount_due|floatformat:2 }} руб.</strong></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
<div class="row mb-2">
|
||||||
<div class="col-12">
|
<div class="col-12">
|
||||||
@@ -349,12 +343,6 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row mb-2">
|
|
||||||
<div class="col-12">
|
|
||||||
<strong>Способ оплаты:</strong><br>
|
|
||||||
{{ order.get_payment_method_display }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -368,10 +356,13 @@
|
|||||||
<ul class="list-group list-group-flush">
|
<ul class="list-group list-group-flush">
|
||||||
{% for payment in order.payments.all %}
|
{% for payment in order.payments.all %}
|
||||||
<li class="list-group-item">
|
<li class="list-group-item">
|
||||||
<div><strong>{{ payment.amount }} руб.</strong></div>
|
<div><strong>{{ payment.amount|floatformat:2 }} руб.</strong></div>
|
||||||
<small class="text-muted">
|
<small class="text-muted">
|
||||||
{{ payment.payment_date|date:"d.m.Y H:i" }}<br>
|
{{ payment.payment_date|date:"d.m.Y H:i" }}<br>
|
||||||
{{ payment.get_payment_method_display }}
|
{{ payment.payment_method.name }}
|
||||||
|
{% if payment.notes %}
|
||||||
|
<br><em>{{ payment.notes }}</em>
|
||||||
|
{% endif %}
|
||||||
{% if payment.created_by %}
|
{% if payment.created_by %}
|
||||||
<br>Принял: {{ payment.created_by.get_full_name }}
|
<br>Принял: {{ payment.created_by.get_full_name }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -111,7 +111,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form method="post" id="order-form" {% if is_draft %}data-is-draft="true"{% endif %}>
|
<form method="post" id="order-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|
||||||
<!-- Основная информация -->
|
<!-- Основная информация -->
|
||||||
@@ -247,7 +247,7 @@
|
|||||||
Изменена
|
Изменена
|
||||||
</span>
|
</span>
|
||||||
<small class="text-muted original-price-info position-absolute" style="display: none; top: calc(100% + 2px); left: 0; white-space: nowrap;">
|
<small class="text-muted original-price-info position-absolute" style="display: none; top: calc(100% + 2px); left: 0; white-space: nowrap;">
|
||||||
Оригинальная: <span class="original-price-value"></span> руб.
|
Оригинальная: <span class="original-price-value"></span> руб.
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -275,7 +275,7 @@
|
|||||||
<div id="order-items-total-section" class="border-top pt-3 mt-3 mb-3">
|
<div id="order-items-total-section" class="border-top pt-3 mt-3 mb-3">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p class="mb-0 text-muted">Сумма товаров:</p>
|
<p class="mb-0 text-muted"><i class="bi bi-calculator"></i> Сумма товаров:</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<h5 class="mb-0 text-primary">
|
<h5 class="mb-0 text-primary">
|
||||||
@@ -317,7 +317,7 @@
|
|||||||
Изменена
|
Изменена
|
||||||
</span>
|
</span>
|
||||||
<small class="text-muted original-price-info position-absolute" style="display: none; top: calc(100% + 2px); left: 0; white-space: nowrap;">
|
<small class="text-muted original-price-info position-absolute" style="display: none; top: calc(100% + 2px); left: 0; white-space: nowrap;">
|
||||||
Оригинальная: <span class="original-price-value"></span> руб.
|
Оригинальная: <span class="original-price-value"></span> руб.
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -544,7 +544,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
<small class="d-block text-muted mt-1">
|
<small class="d-block text-muted mt-1">
|
||||||
<i class="bi bi-info-circle"></i>
|
<i class="bi bi-info-circle"></i>
|
||||||
Оставьте пустым для автоматического расчета (бесплатно от 100 руб., иначе 15 руб.)
|
Оставьте пустым для автоматического расчета стоимости доставки
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -572,15 +572,10 @@
|
|||||||
<!-- Оплата (смешанная оплата) -->
|
<!-- Оплата (смешанная оплата) -->
|
||||||
<div class="card mb-3">
|
<div class="card mb-3">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="mb-0">Оплата</h5>
|
<h5 class="mb-0"><i class="bi bi-credit-card"></i> Оплата</h5>
|
||||||
<div>
|
<button type="button" class="btn btn-sm btn-success" id="add-payment-btn">
|
||||||
<span class="badge bg-{% if order.payment_status == 'paid' %}success{% elif order.payment_status == 'partial' %}warning{% else %}danger{% endif %} me-2">
|
<i class="bi bi-plus-circle"></i> Добавить платеж
|
||||||
{{ order.get_payment_status_display }}
|
</button>
|
||||||
</span>
|
|
||||||
<button type="button" class="btn btn-sm btn-success" id="add-payment-btn">
|
|
||||||
<i class="bi bi-plus-circle"></i> Добавить платеж
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
@@ -588,7 +583,7 @@
|
|||||||
{% if order.customer %}
|
{% if order.customer %}
|
||||||
<div class="alert alert-info d-flex justify-content-between align-items-center">
|
<div class="alert alert-info d-flex justify-content-between align-items-center">
|
||||||
<div>
|
<div>
|
||||||
<strong>Кошелёк клиента:</strong>
|
<strong><i class="bi bi-wallet2"></i> Кошелёк клиента:</strong>
|
||||||
{% if order.customer.wallet_balance > 0 %}
|
{% if order.customer.wallet_balance > 0 %}
|
||||||
<span class="text-success fw-bold">{{ order.customer.wallet_balance|floatformat:2 }} руб.</span>
|
<span class="text-success fw-bold">{{ order.customer.wallet_balance|floatformat:2 }} руб.</span>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -657,7 +652,7 @@
|
|||||||
<div id="payments-total-section" class="border-top pt-3 mt-3 mb-3">
|
<div id="payments-total-section" class="border-top pt-3 mt-3 mb-3">
|
||||||
<div class="row align-items-center">
|
<div class="row align-items-center">
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<p class="mb-0 text-muted">Внесено платежей:</p>
|
<p class="mb-0 text-muted"><i class="bi bi-cash-stack"></i> Внесено платежей:</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-auto">
|
<div class="col-auto">
|
||||||
<h5 class="mb-0 text-success">
|
<h5 class="mb-0 text-success">
|
||||||
@@ -759,37 +754,90 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Найти существующую форму платежа без метода, иначе добавить новую
|
// Всегда добавляем новую строку платежа
|
||||||
let formEl = document.querySelector('#payments-container .payment-form:last-child');
|
const formEl = addPaymentRow();
|
||||||
if (!formEl) {
|
|
||||||
formEl = addPaymentRow();
|
|
||||||
}
|
|
||||||
const sel = formEl.querySelector('select[id^="id_payments-"][id$="-payment_method"]');
|
const sel = formEl.querySelector('select[id^="id_payments-"][id$="-payment_method"]');
|
||||||
const amt = formEl.querySelector('input[id^="id_payments-"][id$="-amount"]');
|
const amt = formEl.querySelector('input[id^="id_payments-"][id$="-amount"]');
|
||||||
|
const notes = formEl.querySelector('textarea[id^="id_payments-"][id$="-notes"]');
|
||||||
|
|
||||||
selectAccountBalance(sel);
|
// Загружаем список способов оплаты
|
||||||
|
if (sel) {
|
||||||
|
fetch('/products/api/payment-methods/')
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
sel.innerHTML = '<option value="">---------</option>';
|
||||||
|
data.forEach(method => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = method.id;
|
||||||
|
option.textContent = method.name;
|
||||||
|
sel.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
// После загрузки устанавливаем "С баланса счёта"
|
||||||
|
for (let i = 0; i < sel.options.length; i++) {
|
||||||
|
if (sel.options[i].textContent.trim() === 'С баланса счёта') {
|
||||||
|
sel.value = sel.options[i].value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error('Error loading payment methods:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Проставляем сумму
|
||||||
if (amt) {
|
if (amt) {
|
||||||
amt.value = amount.toFixed(2);
|
const maxUsable = Math.min(walletBalance, amountDue);
|
||||||
amt.setAttribute('max', Math.min(walletBalance, amountDue).toFixed(2));
|
const finalAmount = Math.min(amount, maxUsable);
|
||||||
|
amt.value = finalAmount.toFixed(2);
|
||||||
|
amt.setAttribute('max', maxUsable.toFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Небольшая подсказка в примечания
|
||||||
|
if (notes && !notes.value) {
|
||||||
|
notes.value = 'Оплата из кошелька';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем обработчик удаления
|
||||||
|
const removeBtn = formEl.querySelector('.remove-payment-btn');
|
||||||
|
if (removeBtn) {
|
||||||
|
removeBtn.addEventListener('click', function() {
|
||||||
|
if (!confirm('Вы действительно хотите удалить этот платеж?')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const deleteCheckbox = formEl.querySelector('input[name$="-DELETE"]');
|
||||||
|
const idField = formEl.querySelector('input[name$="-id"]');
|
||||||
|
if (idField && idField.value) {
|
||||||
|
deleteCheckbox.checked = true;
|
||||||
|
formEl.classList.add('deleted');
|
||||||
|
formEl.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
formEl.remove();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const applyMaxBtn = document.getElementById('apply-wallet-max-btn');
|
// Обработчики кнопок применения кошелька
|
||||||
const applyAmountBtn = document.getElementById('apply-wallet-amount-btn');
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
const amountInput = document.getElementById('apply-wallet-amount-input');
|
const applyMaxBtn = document.getElementById('apply-wallet-max-btn');
|
||||||
|
const applyAmountBtn = document.getElementById('apply-wallet-amount-btn');
|
||||||
|
const amountInput = document.getElementById('apply-wallet-amount-input');
|
||||||
|
|
||||||
if (applyMaxBtn) {
|
if (applyMaxBtn) {
|
||||||
applyMaxBtn.addEventListener('click', function() {
|
applyMaxBtn.addEventListener('click', function() {
|
||||||
const maxUsable = Math.min(walletBalance, amountDue);
|
const maxUsable = Math.min(walletBalance, amountDue);
|
||||||
applyWallet(maxUsable);
|
applyWallet(maxUsable);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (applyAmountBtn && amountInput) {
|
if (applyAmountBtn && amountInput) {
|
||||||
applyAmountBtn.addEventListener('click', function() {
|
applyAmountBtn.addEventListener('click', function() {
|
||||||
const val = parseFloat((amountInput.value || '0').replace(',', '.')) || 0;
|
const val = parseFloat((amountInput.value || '0').replace(',', '.')) || 0;
|
||||||
applyWallet(val);
|
applyWallet(val);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Автозаполнение при выборе "С баланса счёта"
|
// Автозаполнение при выборе "С баланса счёта"
|
||||||
document.getElementById('payments-container').addEventListener('change', function(e) {
|
document.getElementById('payments-container').addEventListener('change', function(e) {
|
||||||
@@ -812,7 +860,7 @@
|
|||||||
|
|
||||||
<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"><i class="bi bi-three-dots"></i> Дополнительно</h5>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="mb-3 form-check">
|
<div class="mb-3 form-check">
|
||||||
@@ -833,23 +881,12 @@
|
|||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="d-flex gap-2">
|
<div class="d-flex gap-2">
|
||||||
{% if is_create_page %}
|
{% if is_create_page %}
|
||||||
<!-- На странице создания показываем обе кнопки -->
|
<!-- На странице создания показываем одну кнопку -->
|
||||||
<button type="submit" name="create_order" class="btn btn-primary btn-lg">
|
<button type="submit" class="btn btn-primary btn-lg">
|
||||||
<i class="bi bi-check-circle"></i> Создать заказ
|
<i class="bi bi-check-circle"></i> Создать заказ
|
||||||
</button>
|
</button>
|
||||||
<button type="submit" name="save_as_draft" class="btn btn-secondary btn-lg">
|
|
||||||
<i class="bi bi-save"></i> Сохранить как черновик
|
|
||||||
</button>
|
|
||||||
{% elif is_draft %}
|
|
||||||
<!-- Для черновиков показываем кнопку финализации и обычного сохранения -->
|
|
||||||
<button type="submit" name="finalize_draft" class="btn btn-success btn-lg">
|
|
||||||
<i class="bi bi-check-circle-fill"></i> Финализировать черновик
|
|
||||||
</button>
|
|
||||||
<button type="submit" class="btn btn-primary btn-lg">
|
|
||||||
<i class="bi bi-save"></i> Сохранить изменения
|
|
||||||
</button>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<!-- Для обычных заказов - только сохранение -->
|
<!-- Для любых заказов - только сохранение -->
|
||||||
<button type="submit" class="btn btn-primary btn-lg">
|
<button type="submit" class="btn btn-primary btn-lg">
|
||||||
<i class="bi bi-save"></i> {{ button_text }}
|
<i class="bi bi-save"></i> {{ button_text }}
|
||||||
</button>
|
</button>
|
||||||
@@ -865,27 +902,11 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// Глобально определяем initOrderItemSelect2, чтобы она была доступна при вызове ниже
|
// Глобально определяем initOrderItemSelect2
|
||||||
window.initOrderItemSelect2 = function(element) {
|
window.initOrderItemSelect2 = function(element) {
|
||||||
console.log('[initOrderItemSelect2] Вызвана для элемента:', element);
|
|
||||||
|
|
||||||
// Проверяем доступность jQuery
|
|
||||||
if (typeof $ === 'undefined') {
|
|
||||||
console.error('[initOrderItemSelect2] jQuery не загружен!');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const $element = $(element);
|
const $element = $(element);
|
||||||
const formIndex = element.dataset.formIndex;
|
const formIndex = element.dataset.formIndex;
|
||||||
console.log('[initOrderItemSelect2] formIndex:', formIndex);
|
|
||||||
|
|
||||||
// Проверяем, что функция initProductSelect2 доступна
|
|
||||||
if (typeof window.initProductSelect2 !== 'function') {
|
|
||||||
console.error('[initOrderItemSelect2] window.initProductSelect2 не определена. Убедитесь, что select2-product-search.js загружен.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[initOrderItemSelect2] Инициализация Select2 через initProductSelect2...');
|
|
||||||
// Инициализируем Select2 с AJAX поиском
|
// Инициализируем Select2 с AJAX поиском
|
||||||
window.initProductSelect2(
|
window.initProductSelect2(
|
||||||
element,
|
element,
|
||||||
@@ -945,30 +966,21 @@ window.initOrderItemSelect2 = function(element) {
|
|||||||
form.querySelector('[name$="-product_kit"]').value = '';
|
form.querySelector('[name$="-product_kit"]').value = '';
|
||||||
form.querySelector('[name$="-price"]').value = '';
|
form.querySelector('[name$="-price"]').value = '';
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[initOrderItemSelect2] Инициализация завершена успешно');
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Ждем пока jQuery загрузится
|
// Ждем пока jQuery загрузится
|
||||||
function initCustomerSelect2() {
|
function initCustomerSelect2() {
|
||||||
if (typeof $ === 'undefined') {
|
if (typeof $ === 'undefined') {
|
||||||
console.log('jQuery еще не загружен, ждем...');
|
|
||||||
setTimeout(initCustomerSelect2, 100);
|
setTimeout(initCustomerSelect2, 100);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('=== ИНИЦИАЛИЗАЦИЯ SELECT2 ДЛЯ CUSTOMER ===');
|
|
||||||
|
|
||||||
const $customerSelect = $('#id_customer');
|
const $customerSelect = $('#id_customer');
|
||||||
const ajaxUrl = '{% url "customers:api-search-customers" %}';
|
const ajaxUrl = '{% url "customers:api-search-customers" %}';
|
||||||
|
|
||||||
console.log('1. Поле customer найдено:', $customerSelect.length);
|
// Сохраняем текущее значение перед очисткой
|
||||||
console.log('2. AJAX URL:', ajaxUrl);
|
|
||||||
|
|
||||||
// Сохраняем текущее значение перед очисткой (может быть при редактировании черновика)
|
|
||||||
const currentValue = $customerSelect.val();
|
const currentValue = $customerSelect.val();
|
||||||
const currentText = $customerSelect.find(':selected').text();
|
const currentText = $customerSelect.find(':selected').text();
|
||||||
console.log('Сохраняем текущее значение:', currentValue, currentText);
|
|
||||||
|
|
||||||
// НЕ очищаем, если у нас есть текущий выбранный клиент
|
// НЕ очищаем, если у нас есть текущий выбранный клиент
|
||||||
if (!currentValue) {
|
if (!currentValue) {
|
||||||
@@ -989,19 +1001,14 @@ function initCustomerSelect2() {
|
|||||||
dataType: 'json',
|
dataType: 'json',
|
||||||
delay: 500,
|
delay: 500,
|
||||||
data: function(params) {
|
data: function(params) {
|
||||||
console.log('3. AJAX запрос с query:', params.term);
|
|
||||||
return {
|
return {
|
||||||
q: params.term || ''
|
q: params.term || ''
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
processResults: function(data) {
|
processResults: function(data) {
|
||||||
console.log('4. AJAX ответ получен:', data);
|
|
||||||
return {
|
return {
|
||||||
results: data.results || []
|
results: data.results || []
|
||||||
};
|
};
|
||||||
},
|
|
||||||
error: function(xhr, status, error) {
|
|
||||||
console.error('5. AJAX ОШИБКА:', status, error, xhr.responseText);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
templateResult: formatCustomerOption,
|
templateResult: formatCustomerOption,
|
||||||
@@ -1009,22 +1016,13 @@ function initCustomerSelect2() {
|
|||||||
escapeMarkup: function(markup) { return markup; }
|
escapeMarkup: function(markup) { return markup; }
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('6. Select2 инициализирован');
|
// Восстанавливаем значение если оно было
|
||||||
|
|
||||||
// Восстанавливаем значение если оно было (для редактирования черновика)
|
|
||||||
if (currentValue && currentText) {
|
if (currentValue && currentText) {
|
||||||
console.log('Восстанавливаем значение:', currentValue, 'Текст:', currentText);
|
|
||||||
|
|
||||||
// Select2 видит option элемент в DOM (так как мы его не очищали)
|
|
||||||
// Просто устанавливаем значение через Select2 API
|
|
||||||
$customerSelect.val(currentValue);
|
$customerSelect.val(currentValue);
|
||||||
|
|
||||||
console.log('Значение восстановлено:', $customerSelect.val());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Select2 готов и есть предзаполненное значение
|
// Select2 готов и есть предзаполненное значение
|
||||||
if (currentValue && window.DraftCreator) {
|
if (currentValue && window.DraftCreator) {
|
||||||
console.log('7. Уведомляем DraftCreator о предзаполненном клиенте');
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
window.DraftCreator.triggerDraftCreation();
|
window.DraftCreator.triggerDraftCreation();
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -1032,68 +1030,46 @@ function initCustomerSelect2() {
|
|||||||
|
|
||||||
// Слушаем события
|
// Слушаем события
|
||||||
$customerSelect.on('select2:open', function(e) {
|
$customerSelect.on('select2:open', function(e) {
|
||||||
console.log('7. Dropdown открыт');
|
|
||||||
// Устанавливаем фокус на input field
|
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
$('.select2-search__field:visible').first().focus();
|
$('.select2-search__field:visible').first().focus();
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
$customerSelect.on('select2:searching', function(e) {
|
// Обработчик для перехвата ПЕРЕД выбором
|
||||||
console.log('8. Поиск с term:', e.params.term);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обработчик для перехвата ПЕРЕД выбором (используется для фальшивых опций)
|
|
||||||
$customerSelect.on('select2:selecting', function(e) {
|
$customerSelect.on('select2:selecting', function(e) {
|
||||||
console.log('9. Событие select2:selecting, e.params:', e.params);
|
|
||||||
|
|
||||||
// Проверяем наличие e.params и e.params.data
|
// Проверяем наличие e.params и e.params.data
|
||||||
if (!e.params || !e.params.data) {
|
if (!e.params || !e.params.data) {
|
||||||
console.log('9a. Нет данных в e.params на selecting, пропускаем');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = e.params.data;
|
const data = e.params.data;
|
||||||
console.log('9b. Попытка выбрать элемент (перед выбором):', data);
|
|
||||||
|
|
||||||
if (data.is_create_option || data.id === '__create_new__') {
|
if (data.is_create_option || data.id === '__create_new__') {
|
||||||
console.log('9c. Это опция создания клиента - предотвращаем выбор и открываем модаль');
|
|
||||||
// Предотвращаем выбор этой опции
|
// Предотвращаем выбор этой опции
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
// Закрываем dropdown
|
|
||||||
$customerSelect.select2('close');
|
$customerSelect.select2('close');
|
||||||
// Очищаем значение
|
|
||||||
$customerSelect.val(null).trigger('change.select2');
|
$customerSelect.val(null).trigger('change.select2');
|
||||||
// Открываем модаль
|
|
||||||
window.openCreateCustomerModal(data.search_text);
|
window.openCreateCustomerModal(data.search_text);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('9d. Обычный клиент, разрешаем выбор');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
$customerSelect.on('select2:select', function(e) {
|
$customerSelect.on('select2:select', function(e) {
|
||||||
console.log('10. Событие select2:select, e.params:', e.params);
|
|
||||||
|
|
||||||
// Проверяем наличие e.params и e.params.data
|
// Проверяем наличие e.params и e.params.data
|
||||||
if (!e.params || !e.params.data) {
|
if (!e.params || !e.params.data) {
|
||||||
console.log('10a. Нет данных в e.params, пропускаем обработку');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = e.params.data;
|
const data = e.params.data;
|
||||||
console.log('10b. Выбран элемент:', data);
|
|
||||||
|
|
||||||
if (data.is_create_option || data.id === '__create_new__') {
|
if (data.is_create_option || data.id === '__create_new__') {
|
||||||
console.log('11. Открываем модальное окно для создания клиента');
|
|
||||||
this.value = '';
|
this.value = '';
|
||||||
// Триггерим нативное change событие
|
|
||||||
const changeEvent = new Event('change', { bubbles: true });
|
const changeEvent = new Event('change', { bubbles: true });
|
||||||
this.dispatchEvent(changeEvent);
|
this.dispatchEvent(changeEvent);
|
||||||
window.openCreateCustomerModal(data.search_text);
|
window.openCreateCustomerModal(data.search_text);
|
||||||
} else {
|
} else {
|
||||||
// Триггерим нативное change событие для других обработчиков
|
|
||||||
console.log('12. Триггерим нативное change событие для customer ID:', data.id);
|
|
||||||
const changeEvent = new Event('change', { bubbles: true });
|
const changeEvent = new Event('change', { bubbles: true });
|
||||||
this.dispatchEvent(changeEvent);
|
this.dispatchEvent(changeEvent);
|
||||||
}
|
}
|
||||||
@@ -1195,58 +1171,43 @@ if (typeof $ !== 'undefined') {
|
|||||||
// ВАЖНО: Этот код должен быть ВНЕ jQuery document.ready,
|
// ВАЖНО: Этот код должен быть ВНЕ jQuery document.ready,
|
||||||
// чтобы выполниться после полной загрузки DOM
|
// чтобы выполниться после полной загрузки DOM
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
console.log('[DELIVERY TYPE] DOM loaded, initializing...');
|
|
||||||
|
|
||||||
const deliveryTypeRadios = document.querySelectorAll('input[name="delivery-type"]');
|
const deliveryTypeRadios = document.querySelectorAll('input[name="delivery-type"]');
|
||||||
const isDeliveryCheckbox = document.getElementById('{{ form.is_delivery.id_for_label }}');
|
const isDeliveryCheckbox = document.getElementById('{{ form.is_delivery.id_for_label }}');
|
||||||
const deliveryModeFields = document.getElementById('delivery-mode-fields');
|
const deliveryModeFields = document.getElementById('delivery-mode-fields');
|
||||||
const pickupFields = document.getElementById('pickup-fields');
|
const pickupFields = document.getElementById('pickup-fields');
|
||||||
|
|
||||||
function syncDeliveryTypeFromRadio() {
|
function syncDeliveryTypeFromRadio() {
|
||||||
// Синхронизирует чекбокс И UI с выбранной радиокнопкой
|
|
||||||
const selectedType = document.querySelector('input[name="delivery-type"]:checked').value;
|
const selectedType = document.querySelector('input[name="delivery-type"]:checked').value;
|
||||||
|
|
||||||
if (selectedType === 'delivery') {
|
if (selectedType === 'delivery') {
|
||||||
// Доставка
|
|
||||||
isDeliveryCheckbox.checked = true;
|
isDeliveryCheckbox.checked = true;
|
||||||
deliveryModeFields.style.display = 'block';
|
deliveryModeFields.style.display = 'block';
|
||||||
pickupFields.style.display = 'none';
|
pickupFields.style.display = 'none';
|
||||||
console.log('[DELIVERY TYPE] Delivery selected');
|
|
||||||
} else {
|
} else {
|
||||||
// Самовывоз
|
|
||||||
isDeliveryCheckbox.checked = false;
|
isDeliveryCheckbox.checked = false;
|
||||||
deliveryModeFields.style.display = 'none';
|
deliveryModeFields.style.display = 'none';
|
||||||
pickupFields.style.display = 'block';
|
pickupFields.style.display = 'block';
|
||||||
console.log('[DELIVERY TYPE] Pickup selected');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function syncUIFromCheckbox() {
|
function syncUIFromCheckbox() {
|
||||||
// Синхронизирует ТОЛЬКО UI (не трогает чекбокс) с текущим значением чекбокса
|
|
||||||
if (isDeliveryCheckbox.checked) {
|
if (isDeliveryCheckbox.checked) {
|
||||||
// Синхронизируем радиокнопки
|
|
||||||
document.getElementById('delivery-type-delivery').checked = true;
|
document.getElementById('delivery-type-delivery').checked = true;
|
||||||
// Синхронизируем видимость секций
|
|
||||||
deliveryModeFields.style.display = 'block';
|
deliveryModeFields.style.display = 'block';
|
||||||
pickupFields.style.display = 'none';
|
pickupFields.style.display = 'none';
|
||||||
console.log('[DELIVERY TYPE] UI synced: showing delivery fields');
|
|
||||||
} else {
|
} else {
|
||||||
// Синхронизируем радиокнопки
|
|
||||||
document.getElementById('delivery-type-pickup').checked = true;
|
document.getElementById('delivery-type-pickup').checked = true;
|
||||||
// Синхронизируем видимость секций
|
|
||||||
deliveryModeFields.style.display = 'none';
|
deliveryModeFields.style.display = 'none';
|
||||||
pickupFields.style.display = 'block';
|
pickupFields.style.display = 'block';
|
||||||
console.log('[DELIVERY TYPE] UI synced: showing pickup fields');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработчики для кнопок - при клике синхронизируем чекбокс с радиокнопками
|
// Обработчики для кнопок
|
||||||
deliveryTypeRadios.forEach(radio => {
|
deliveryTypeRadios.forEach(radio => {
|
||||||
radio.addEventListener('change', syncDeliveryTypeFromRadio);
|
radio.addEventListener('change', syncDeliveryTypeFromRadio);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Инициализация при загрузке - синхронизируем UI с текущим значением чекбокса (из формы)
|
// Инициализация при загрузке
|
||||||
console.log('[DELIVERY TYPE] Initializing delivery type, checkbox value:', isDeliveryCheckbox.checked);
|
|
||||||
syncUIFromCheckbox();
|
syncUIFromCheckbox();
|
||||||
|
|
||||||
// Показ/скрытие полей получателя
|
// Показ/скрытие полей получателя
|
||||||
@@ -1262,44 +1223,27 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
customerIsRecipientCheckbox.addEventListener('change', toggleRecipientFields);
|
customerIsRecipientCheckbox.addEventListener('change', toggleRecipientFields);
|
||||||
toggleRecipientFields(); // Инициализация при загрузке
|
toggleRecipientFields();
|
||||||
|
|
||||||
// === РАСЧЁТ ИТОГОВОЙ СУММЫ ТОВАРОВ ===
|
// === РАСЧЁТ ИТОГОВОЙ СУММЫ ТОВАРОВ ===
|
||||||
function calculateOrderItemsTotal() {
|
function calculateOrderItemsTotal() {
|
||||||
// Собираем все видимые (не удалённые) формы товаров
|
|
||||||
const visibleForms = Array.from(document.querySelectorAll('.order-item-form'))
|
const visibleForms = Array.from(document.querySelectorAll('.order-item-form'))
|
||||||
.filter(form => !form.classList.contains('deleted'));
|
.filter(form => !form.classList.contains('deleted'));
|
||||||
|
|
||||||
let total = 0;
|
let total = 0;
|
||||||
|
|
||||||
console.log('[TOTAL] Calculating total for', visibleForms.length, 'forms');
|
|
||||||
|
|
||||||
// Для каждого товара: количество × цена
|
|
||||||
visibleForms.forEach((form, index) => {
|
visibleForms.forEach((form, index) => {
|
||||||
const quantityField = form.querySelector('[name$="-quantity"]');
|
const quantityField = form.querySelector('[name$="-quantity"]');
|
||||||
const priceField = form.querySelector('[name$="-price"]');
|
const priceField = form.querySelector('[name$="-price"]');
|
||||||
|
|
||||||
console.log(`[TOTAL] Form ${index}:`, form);
|
|
||||||
console.log(`[TOTAL] Form ${index}: quantityField=${quantityField}, priceField=${priceField}`);
|
|
||||||
const allInputs = form.querySelectorAll('input');
|
|
||||||
console.log(`[TOTAL] Form ${index}: All inputs:`, allInputs);
|
|
||||||
allInputs.forEach((input, i) => {
|
|
||||||
console.log(` Input ${i}: name="${input.name}", id="${input.id}", type="${input.type}"`);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (quantityField && priceField) {
|
if (quantityField && priceField) {
|
||||||
const quantity = parseFloat(quantityField.value) || 0;
|
const quantity = parseFloat(quantityField.value) || 0;
|
||||||
// Заменяем запятую на точку для корректного парсинга
|
|
||||||
const priceValue = priceField.value.replace(',', '.');
|
const priceValue = priceField.value.replace(',', '.');
|
||||||
const price = parseFloat(priceValue) || 0;
|
const price = parseFloat(priceValue) || 0;
|
||||||
console.log(`[TOTAL] Form ${index}: quantity=${quantityField.value} (parsed: ${quantity}), price="${priceField.value}" (parsed: ${price}), subtotal=${quantity * price}`);
|
|
||||||
total += quantity * price;
|
total += quantity * price;
|
||||||
} else {
|
|
||||||
console.log(`[TOTAL] Form ${index}: SKIPPED - missing fields!`);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('[TOTAL] Final total:', total);
|
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1308,7 +1252,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const totalElement = document.getElementById('order-items-total-value');
|
const totalElement = document.getElementById('order-items-total-value');
|
||||||
|
|
||||||
if (totalElement) {
|
if (totalElement) {
|
||||||
// Форматируем до 2 знаков после запятой
|
|
||||||
totalElement.textContent = total.toFixed(2);
|
totalElement.textContent = total.toFixed(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1373,19 +1316,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
function addNewForm() {
|
function addNewForm() {
|
||||||
const formCount = parseInt(totalFormsInput.value);
|
const formCount = parseInt(totalFormsInput.value);
|
||||||
|
|
||||||
// Клонируем шаблон
|
|
||||||
const templateContent = emptyFormTemplate.content || emptyFormTemplate;
|
const templateContent = emptyFormTemplate.content || emptyFormTemplate;
|
||||||
const formTemplate = templateContent.querySelector('.order-item-form');
|
const formTemplate = templateContent.querySelector('.order-item-form');
|
||||||
const newForm = formTemplate.cloneNode(true);
|
const newForm = formTemplate.cloneNode(true);
|
||||||
|
|
||||||
// Заменяем __prefix__ на реальный индекс
|
|
||||||
newForm.innerHTML = newForm.innerHTML.replace(/__prefix__/g, formCount);
|
newForm.innerHTML = newForm.innerHTML.replace(/__prefix__/g, formCount);
|
||||||
newForm.dataset.formIndex = formCount;
|
newForm.dataset.formIndex = formCount;
|
||||||
|
|
||||||
// Добавляем форму в контейнер
|
|
||||||
container.appendChild(newForm);
|
container.appendChild(newForm);
|
||||||
|
|
||||||
// Обновляем счетчик форм
|
|
||||||
totalFormsInput.value = formCount + 1;
|
totalFormsInput.value = formCount + 1;
|
||||||
|
|
||||||
// Инициализируем Select2 для новой формы
|
// Инициализируем Select2 для новой формы
|
||||||
@@ -1393,22 +1332,15 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
select2Element.dataset.formIndex = formCount;
|
select2Element.dataset.formIndex = formCount;
|
||||||
if (typeof window.initOrderItemSelect2 === 'function') {
|
if (typeof window.initOrderItemSelect2 === 'function') {
|
||||||
window.initOrderItemSelect2(select2Element);
|
window.initOrderItemSelect2(select2Element);
|
||||||
} else {
|
|
||||||
console.error('window.initOrderItemSelect2 is not available');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализируем отслеживание цены
|
|
||||||
initPriceTracking(newForm);
|
initPriceTracking(newForm);
|
||||||
|
|
||||||
// Добавляем обработчик удаления
|
|
||||||
const removeBtn = newForm.querySelector('.remove-item-btn');
|
const removeBtn = newForm.querySelector('.remove-item-btn');
|
||||||
removeBtn.addEventListener('click', function() {
|
removeBtn.addEventListener('click', function() {
|
||||||
removeForm(newForm);
|
removeForm(newForm);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`Added new form with index ${formCount}`);
|
|
||||||
|
|
||||||
// Обновляем итоговую сумму после добавления формы
|
|
||||||
updateOrderItemsTotal();
|
updateOrderItemsTotal();
|
||||||
|
|
||||||
return newForm;
|
return newForm;
|
||||||
@@ -1416,31 +1348,23 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Функция для удаления формы
|
// Функция для удаления формы
|
||||||
function removeForm(form) {
|
function removeForm(form) {
|
||||||
// Показываем диалог подтверждения
|
|
||||||
if (!confirm('Вы действительно хотите удалить этот товар из заказа?')) {
|
if (!confirm('Вы действительно хотите удалить этот товар из заказа?')) {
|
||||||
return; // Пользователь отменил удаление
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
|
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
|
||||||
const idField = form.querySelector('input[name$="-id"]');
|
const idField = form.querySelector('input[name$="-id"]');
|
||||||
|
|
||||||
// Если форма уже сохранена (есть ID), помечаем на удаление
|
|
||||||
if (idField && idField.value) {
|
if (idField && idField.value) {
|
||||||
deleteCheckbox.checked = true;
|
deleteCheckbox.checked = true;
|
||||||
form.classList.add('deleted');
|
form.classList.add('deleted');
|
||||||
form.style.display = 'none'; // Скрываем форму визуально
|
form.style.display = 'none';
|
||||||
console.log('Form marked for deletion, id:', idField.value);
|
|
||||||
// Обновляем итоговую сумму после удаления
|
|
||||||
updateOrderItemsTotal();
|
updateOrderItemsTotal();
|
||||||
// Триггерим автосохранение для отправки изменений
|
|
||||||
if (typeof window.orderAutosave !== 'undefined' && window.orderAutosave.scheduleAutosave) {
|
if (typeof window.orderAutosave !== 'undefined' && window.orderAutosave.scheduleAutosave) {
|
||||||
window.orderAutosave.scheduleAutosave();
|
window.orderAutosave.scheduleAutosave();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Если форма новая, просто удаляем из DOM
|
|
||||||
form.remove();
|
form.remove();
|
||||||
console.log('Form removed from DOM');
|
|
||||||
// Обновляем итоговую сумму после удаления
|
|
||||||
updateOrderItemsTotal();
|
updateOrderItemsTotal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1461,13 +1385,12 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
addNewForm();
|
addNewForm();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Инициализируем итоговую сумму при загрузке страницы
|
// Инициализируем итоговую сумму
|
||||||
updateOrderItemsTotal();
|
updateOrderItemsTotal();
|
||||||
|
|
||||||
// Валидация перед отправкой (убрана обязательность товаров — можно сохранить пустой заказ)
|
// Валидация перед отправкой
|
||||||
document.getElementById('order-form').addEventListener('submit', function(e) {
|
document.getElementById('order-form').addEventListener('submit', function(e) {
|
||||||
// Валидация отключена — заказ можно сохранить без товаров
|
// Заказ можно сохранить без товаров
|
||||||
// Товары можно добавить позже
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// === ВРЕМЕННЫЕ КОМПЛЕКТЫ ===
|
// === ВРЕМЕННЫЕ КОМПЛЕКТЫ ===
|
||||||
@@ -1745,55 +1668,35 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Функция для добавления нового платежа
|
// Функция для добавления нового платежа
|
||||||
function addNewPayment() {
|
function addNewPayment() {
|
||||||
console.log('[addNewPayment] START, paymentFormCount:', paymentFormCount);
|
|
||||||
|
|
||||||
// ВАЖНО: Получаем HTML из template.content и заменяем __prefix__
|
|
||||||
const tempContainer = document.createElement('div');
|
const tempContainer = document.createElement('div');
|
||||||
tempContainer.appendChild(paymentFormTemplate.content.cloneNode(true));
|
tempContainer.appendChild(paymentFormTemplate.content.cloneNode(true));
|
||||||
const templateHtml = tempContainer.innerHTML;
|
const templateHtml = tempContainer.innerHTML;
|
||||||
console.log('[addNewPayment] templateHtml (first 200 chars):', templateHtml.substring(0, 200));
|
|
||||||
|
|
||||||
const replacedHtml = templateHtml.replace(/__prefix__/g, paymentFormCount);
|
const replacedHtml = templateHtml.replace(/__prefix__/g, paymentFormCount);
|
||||||
console.log('[addNewPayment] replacedHtml (first 200 chars):', replacedHtml.substring(0, 200));
|
|
||||||
|
|
||||||
// Создаем элемент из обработанного HTML
|
|
||||||
const tempDiv = document.createElement('div');
|
const tempDiv = document.createElement('div');
|
||||||
tempDiv.innerHTML = replacedHtml;
|
tempDiv.innerHTML = replacedHtml;
|
||||||
const newPaymentDiv = tempDiv.firstElementChild;
|
const newPaymentDiv = tempDiv.firstElementChild;
|
||||||
|
|
||||||
newPaymentDiv.setAttribute('data-form-index', paymentFormCount);
|
newPaymentDiv.setAttribute('data-form-index', paymentFormCount);
|
||||||
|
|
||||||
// Добавляем в контейнер
|
|
||||||
paymentsContainer.appendChild(newPaymentDiv);
|
paymentsContainer.appendChild(newPaymentDiv);
|
||||||
console.log('[addNewPayment] Added payment form, index:', paymentFormCount);
|
|
||||||
|
|
||||||
// DEBUG: Проверяем что поля действительно в DOM
|
|
||||||
const allInputs = newPaymentDiv.querySelectorAll('input, select, textarea');
|
|
||||||
console.log('[addNewPayment] Total inputs in new form:', allInputs.length);
|
|
||||||
allInputs.forEach((inp, idx) => {
|
|
||||||
console.log(` [${idx}] name="${inp.name}", type="${inp.type || inp.tagName}"`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Обновляем счетчик форм
|
|
||||||
paymentFormCount++;
|
paymentFormCount++;
|
||||||
document.querySelector('[name="payments-TOTAL_FORMS"]').value = paymentFormCount;
|
document.querySelector('[name="payments-TOTAL_FORMS"]').value = paymentFormCount;
|
||||||
|
|
||||||
// Добавляем обработчик удаления
|
|
||||||
const removeBtn = newPaymentDiv.querySelector('.remove-payment-btn');
|
const removeBtn = newPaymentDiv.querySelector('.remove-payment-btn');
|
||||||
removeBtn.addEventListener('click', function() {
|
removeBtn.addEventListener('click', function() {
|
||||||
removePayment(newPaymentDiv);
|
removePayment(newPaymentDiv);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Добавляем обработчики для автоматического пересчета
|
|
||||||
const amountField = newPaymentDiv.querySelector('[name$="-amount"]');
|
const amountField = newPaymentDiv.querySelector('[name$="-amount"]');
|
||||||
if (amountField) {
|
if (amountField) {
|
||||||
amountField.addEventListener('input', updatePaymentsTotal);
|
amountField.addEventListener('input', updatePaymentsTotal);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Загружаем payment methods в select
|
|
||||||
loadPaymentMethods(newPaymentDiv.querySelector('select[name$="-payment_method"]'));
|
loadPaymentMethods(newPaymentDiv.querySelector('select[name$="-payment_method"]'));
|
||||||
|
|
||||||
// Обновляем итоговую сумму
|
|
||||||
updatePaymentsTotal();
|
updatePaymentsTotal();
|
||||||
|
|
||||||
return newPaymentDiv;
|
return newPaymentDiv;
|
||||||
@@ -1808,19 +1711,14 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
|
const deleteCheckbox = form.querySelector('input[name$="-DELETE"]');
|
||||||
const idField = form.querySelector('input[name$="-id"]');
|
const idField = form.querySelector('input[name$="-id"]');
|
||||||
|
|
||||||
// Если форма уже сохранена (есть ID), помечаем на удаление
|
|
||||||
if (idField && idField.value) {
|
if (idField && idField.value) {
|
||||||
deleteCheckbox.checked = true;
|
deleteCheckbox.checked = true;
|
||||||
form.classList.add('deleted');
|
form.classList.add('deleted');
|
||||||
form.style.display = 'none';
|
form.style.display = 'none';
|
||||||
console.log('Payment form marked for deletion, id:', idField.value);
|
|
||||||
} else {
|
} else {
|
||||||
// Если форма новая, просто удаляем из DOM
|
|
||||||
form.remove();
|
form.remove();
|
||||||
console.log('Payment form removed from DOM');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем итоговую сумму
|
|
||||||
updatePaymentsTotal();
|
updatePaymentsTotal();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1860,10 +1758,10 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
field.addEventListener('input', updatePaymentsTotal);
|
field.addEventListener('input', updatePaymentsTotal);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Инициализируем итоговую сумму при загрузке страницы
|
// Инициализируем итоговую сумму
|
||||||
updatePaymentsTotal();
|
updatePaymentsTotal();
|
||||||
|
|
||||||
// Закрытие обработчика DOMContentLoaded для управления типом доставки и остальных функций
|
// Закрытие DOMContentLoaded
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -2236,45 +2134,6 @@ if (!document.getElementById('notification-styles')) {
|
|||||||
const form = document.getElementById('order-form');
|
const form = document.getElementById('order-form');
|
||||||
if (form) {
|
if (form) {
|
||||||
form.addEventListener('submit', function(e) {
|
form.addEventListener('submit', function(e) {
|
||||||
console.log('[FORM SUBMIT] Form is being submitted');
|
|
||||||
console.log('[FORM SUBMIT] TOTAL_FORMS:', document.querySelector('[name="payments-TOTAL_FORMS"]')?.value);
|
|
||||||
|
|
||||||
// Проверяем payment поля в DOM ПЕРЕД submit
|
|
||||||
console.log('[FORM SUBMIT] Checking payments container...');
|
|
||||||
const paymentsContainer = document.getElementById('payments-container');
|
|
||||||
console.log('[FORM SUBMIT] payments-container exists:', !!paymentsContainer);
|
|
||||||
console.log('[FORM SUBMIT] payments-container childElementCount:', paymentsContainer?.childElementCount);
|
|
||||||
console.log('[FORM SUBMIT] form.contains(paymentsContainer):', form.contains(paymentsContainer));
|
|
||||||
|
|
||||||
// Проверяем что внутри контейнера
|
|
||||||
if (paymentsContainer && paymentsContainer.childElementCount > 0) {
|
|
||||||
const firstChild = paymentsContainer.firstElementChild;
|
|
||||||
console.log('[FORM SUBMIT] First child of container:', firstChild);
|
|
||||||
const inputs = firstChild.querySelectorAll('input, select, textarea');
|
|
||||||
console.log('[FORM SUBMIT] Inputs in first child:', inputs.length);
|
|
||||||
inputs.forEach((inp, idx) => {
|
|
||||||
console.log(` [${idx}] name="${inp.name}", value="${inp.value}", form="${inp.form?.id}"`);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[FORM SUBMIT] Payment fields in DOM (via form.querySelectorAll):');
|
|
||||||
const paymentInputs = form.querySelectorAll('[name^="payments-"]');
|
|
||||||
console.log('[FORM SUBMIT] Found payment inputs:', paymentInputs.length);
|
|
||||||
paymentInputs.forEach(inp => {
|
|
||||||
const isDisabled = inp.disabled ? ' [DISABLED]' : '';
|
|
||||||
const val = inp.type === 'checkbox' ? inp.checked : inp.value;
|
|
||||||
console.log(` ${inp.name} = "${val}"${isDisabled}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Логируем все payment поля в FormData
|
|
||||||
const formData = new FormData(form);
|
|
||||||
console.log('[FORM SUBMIT] Payment fields in FormData:');
|
|
||||||
for (let [key, value] of formData.entries()) {
|
|
||||||
if (key.startsWith('payments-')) {
|
|
||||||
console.log(` ${key} = ${value}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
isSubmitting = true;
|
isSubmitting = true;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -2285,8 +2144,8 @@ if (!document.getElementById('notification-styles')) {
|
|||||||
return; // Не показываем предупреждение если форма отправляется
|
return; // Не показываем предупреждение если форма отправляется
|
||||||
}
|
}
|
||||||
|
|
||||||
{% if is_create_page or is_draft %}
|
{% if is_create_page %}
|
||||||
// Только для создания заказа и редактирования черновика
|
// Только для создания заказа
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.returnValue = 'Несохраненные данные будут потеряны. Вы уверены?';
|
e.returnValue = 'Несохраненные данные будут потеряны. Вы уверены?';
|
||||||
return e.returnValue;
|
return e.returnValue;
|
||||||
|
|||||||
@@ -80,15 +80,8 @@ def order_create(request):
|
|||||||
address.save()
|
address.save()
|
||||||
order.delivery_address = address
|
order.delivery_address = address
|
||||||
|
|
||||||
# Проверяем какая кнопка нажата
|
# Статус берём из формы (в том числе может быть "Черновик")
|
||||||
if 'save_as_draft' in request.POST:
|
order.modified_by = request.user
|
||||||
# Кнопка "Сохранить как черновик"
|
|
||||||
from .services.order_status_service import OrderStatusService
|
|
||||||
order.status = OrderStatusService.get_draft_status()
|
|
||||||
order.modified_by = request.user
|
|
||||||
else:
|
|
||||||
# Кнопка "Создать заказ" - статус из формы или NULL
|
|
||||||
order.modified_by = request.user
|
|
||||||
|
|
||||||
# Сохраняем заказ в БД (теперь у него есть pk)
|
# Сохраняем заказ в БД (теперь у него есть pk)
|
||||||
order.save()
|
order.save()
|
||||||
@@ -107,7 +100,12 @@ def order_create(request):
|
|||||||
p.order = order
|
p.order = order
|
||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
|
# Обрабатываем удалённые платежи
|
||||||
|
from customers.services.wallet_service import WalletService
|
||||||
for obj in payment_formset.deleted_objects:
|
for obj in payment_formset.deleted_objects:
|
||||||
|
# Если удаляем платёж из кошелька - возвращаем сумму обратно
|
||||||
|
if hasattr(obj, 'payment_method') and obj.payment_method and getattr(obj.payment_method, 'code', '') == 'account_balance':
|
||||||
|
WalletService.refund_wallet_payment(order, obj.amount, request.user)
|
||||||
obj.delete()
|
obj.delete()
|
||||||
|
|
||||||
# Пересчитываем стоимость доставки если она не установлена вручную
|
# Пересчитываем стоимость доставки если она не установлена вручную
|
||||||
@@ -115,18 +113,15 @@ def order_create(request):
|
|||||||
if not delivery_cost or delivery_cost <= 0:
|
if not delivery_cost or delivery_cost <= 0:
|
||||||
order.reset_delivery_cost()
|
order.reset_delivery_cost()
|
||||||
|
|
||||||
# Пересчитываем итоговую сумму и обновляем статус оплаты
|
# Пересчитываем сумму оплачено и итоговую стоимость
|
||||||
|
order.amount_paid = sum(p.amount for p in order.payments.all())
|
||||||
order.calculate_total()
|
order.calculate_total()
|
||||||
order.update_payment_status()
|
order.update_payment_status()
|
||||||
|
|
||||||
# Обрабатываем переплату (если amount_paid > total_amount)
|
# Обрабатываем переплату (если amount_paid > total_amount)
|
||||||
from customers.services.wallet_service import WalletService
|
|
||||||
WalletService.add_overpayment(order, request.user)
|
WalletService.add_overpayment(order, request.user)
|
||||||
|
|
||||||
if order.is_draft():
|
messages.success(request, f'Заказ #{order.order_number} успешно создан!')
|
||||||
messages.success(request, f'Черновик #{order.order_number} успешно создан!')
|
|
||||||
else:
|
|
||||||
messages.success(request, f'Заказ #{order.order_number} успешно создан!')
|
|
||||||
return redirect('orders:order-detail', order_number=order.order_number)
|
return redirect('orders:order-detail', order_number=order.order_number)
|
||||||
else:
|
else:
|
||||||
messages.error(request, 'Пожалуйста, исправьте ошибки в форме.')
|
messages.error(request, 'Пожалуйста, исправьте ошибки в форме.')
|
||||||
@@ -172,102 +167,63 @@ def order_update(request, order_number):
|
|||||||
if form.is_valid() and formset.is_valid() and payment_formset.is_valid():
|
if form.is_valid() and formset.is_valid() and payment_formset.is_valid():
|
||||||
order = form.save(commit=False)
|
order = form.save(commit=False)
|
||||||
|
|
||||||
# Если черновик финализируется
|
# Обрабатываем адрес доставки
|
||||||
if 'finalize_draft' in request.POST and order.is_draft():
|
if order.is_delivery:
|
||||||
from .services.order_status_service import OrderStatusService
|
address = AddressService.process_address_from_form(order, form.cleaned_data)
|
||||||
# Переводим в статус "Новый"
|
if address:
|
||||||
order.status = OrderStatusService.get_new_status()
|
# Если адрес не существует в БД, сохраняем его
|
||||||
order.modified_by = request.user
|
if not address.pk:
|
||||||
|
address.save()
|
||||||
# Обрабатываем адрес доставки
|
order.delivery_address = address
|
||||||
if order.is_delivery:
|
|
||||||
address = AddressService.process_address_from_form(order, form.cleaned_data)
|
|
||||||
if address:
|
|
||||||
if not address.pk:
|
|
||||||
address.save()
|
|
||||||
order.delivery_address = address
|
|
||||||
|
|
||||||
order.save()
|
|
||||||
formset.save()
|
|
||||||
# Сохраняем платежи (устанавливаем created_by)
|
|
||||||
payment_formset.instance = order
|
|
||||||
unsaved_payments = payment_formset.save(commit=False)
|
|
||||||
|
|
||||||
for p in unsaved_payments:
|
|
||||||
if p.created_by_id is None:
|
|
||||||
p.created_by = request.user
|
|
||||||
p.order = order
|
|
||||||
p.save()
|
|
||||||
|
|
||||||
for obj in payment_formset.deleted_objects:
|
|
||||||
obj.delete()
|
|
||||||
|
|
||||||
# Пересчитываем итоговую сумму и обновляем статус оплаты
|
|
||||||
order.calculate_total()
|
|
||||||
order.update_payment_status()
|
|
||||||
|
|
||||||
# Обрабатываем переплату (если amount_paid > total_amount)
|
|
||||||
from customers.services.wallet_service import WalletService
|
|
||||||
WalletService.add_overpayment(order, request.user)
|
|
||||||
|
|
||||||
messages.success(request, f'Черновик #{order.order_number} успешно завершен и переведен в статус "Новый"!')
|
|
||||||
return redirect('orders:order-detail', order_number=order.order_number)
|
|
||||||
else:
|
|
||||||
# Обрабатываем адрес доставки
|
|
||||||
if order.is_delivery:
|
|
||||||
address = AddressService.process_address_from_form(order, form.cleaned_data)
|
|
||||||
if address:
|
|
||||||
# Если адрес не существует в БД, сохраняем его
|
|
||||||
if not address.pk:
|
|
||||||
address.save()
|
|
||||||
order.delivery_address = address
|
|
||||||
else:
|
|
||||||
# Если режим "без адреса", удаляем существующий адрес
|
|
||||||
if order.delivery_address:
|
|
||||||
old_address = order.delivery_address
|
|
||||||
order.delivery_address = None
|
|
||||||
# Удаляем старый адрес, если он больше не используется
|
|
||||||
if old_address and not old_address.order:
|
|
||||||
old_address.delete()
|
|
||||||
else:
|
else:
|
||||||
# Если не доставка, удаляем адрес если он был
|
# Если режим "без адреса", удаляем существующий адрес
|
||||||
if order.delivery_address:
|
if order.delivery_address:
|
||||||
old_address = order.delivery_address
|
old_address = order.delivery_address
|
||||||
order.delivery_address = None
|
order.delivery_address = None
|
||||||
# Удаляем старый адрес
|
# Удаляем старый адрес, если он больше не используется
|
||||||
if old_address and not old_address.order:
|
if old_address and not old_address.order:
|
||||||
old_address.delete()
|
old_address.delete()
|
||||||
|
else:
|
||||||
|
# Если не доставка, удаляем адрес если он был
|
||||||
|
if order.delivery_address:
|
||||||
|
old_address = order.delivery_address
|
||||||
|
order.delivery_address = None
|
||||||
|
# Удаляем старый адрес
|
||||||
|
if old_address and not old_address.order:
|
||||||
|
old_address.delete()
|
||||||
|
|
||||||
order.modified_by = request.user
|
order.modified_by = request.user
|
||||||
order.save()
|
order.save()
|
||||||
formset.save()
|
formset.save()
|
||||||
|
|
||||||
# Сохраняем платежи (устанавливаем created_by)
|
# Сохраняем платежи (устанавливаем created_by)
|
||||||
payment_formset.instance = order
|
payment_formset.instance = order
|
||||||
unsaved_payments = payment_formset.save(commit=False)
|
unsaved_payments = payment_formset.save(commit=False)
|
||||||
|
|
||||||
for p in unsaved_payments:
|
for p in unsaved_payments:
|
||||||
if p.created_by_id is None:
|
if p.created_by_id is None:
|
||||||
p.created_by = request.user
|
p.created_by = request.user
|
||||||
p.order = order
|
p.order = order
|
||||||
p.save()
|
p.save()
|
||||||
|
|
||||||
for obj in payment_formset.deleted_objects:
|
# Обрабатываем удалённые платежи
|
||||||
obj.delete()
|
from customers.services.wallet_service import WalletService
|
||||||
|
for obj in payment_formset.deleted_objects:
|
||||||
|
# Если удаляем платёж из кошелька - возвращаем сумму обратно
|
||||||
|
if hasattr(obj, 'payment_method') and obj.payment_method and getattr(obj.payment_method, 'code', '') == 'account_balance':
|
||||||
|
WalletService.refund_wallet_payment(order, obj.amount, request.user)
|
||||||
|
obj.delete()
|
||||||
|
|
||||||
# Пересчитываем итоговую сумму и обновляем статус оплаты
|
# Пересчитываем сумму оплачено и итоговую стоимость
|
||||||
order.calculate_total()
|
order.amount_paid = sum(p.amount for p in order.payments.all())
|
||||||
order.update_payment_status()
|
order.calculate_total()
|
||||||
|
order.update_payment_status()
|
||||||
|
|
||||||
# Обрабатываем переплату (если amount_paid > total_amount)
|
# Обрабатываем переплату (если amount_paid > total_amount)
|
||||||
from customers.services.wallet_service import WalletService
|
WalletService.add_overpayment(order, request.user)
|
||||||
WalletService.add_overpayment(order, request.user)
|
|
||||||
|
|
||||||
if order.is_draft():
|
messages.success(request, f'Заказ #{order.order_number} успешно обновлен!')
|
||||||
messages.success(request, f'Черновик #{order.order_number} успешно обновлен!')
|
return redirect('orders:order-detail', order_number=order.order_number)
|
||||||
else:
|
|
||||||
messages.success(request, f'Заказ #{order.order_number} успешно обновлен!')
|
|
||||||
return redirect('orders:order-detail', order_number=order.order_number)
|
|
||||||
else:
|
else:
|
||||||
# Логируем ошибки для отладки
|
# Логируем ошибки для отладки
|
||||||
print("\n=== ОШИБКИ ВАЛИДАЦИИ ФОРМЫ ===")
|
print("\n=== ОШИБКИ ВАЛИДАЦИИ ФОРМЫ ===")
|
||||||
@@ -294,9 +250,8 @@ def order_update(request, order_number):
|
|||||||
'formset': formset,
|
'formset': formset,
|
||||||
'payment_formset': payment_formset,
|
'payment_formset': payment_formset,
|
||||||
'order': order,
|
'order': order,
|
||||||
'title': f'Редактирование {"черновика" if order.is_draft() else "заказа"} #{order.order_number}',
|
'title': f'Редактирование заказа #{order.order_number}',
|
||||||
'button_text': 'Сохранить изменения',
|
'button_text': 'Сохранить изменения',
|
||||||
'is_draft': order.is_draft(),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'orders/order_form.html', context)
|
return render(request, 'orders/order_form.html', context)
|
||||||
|
|||||||
Reference in New Issue
Block a user