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