diff --git a/myproject/customers/templates/customers/customer_detail.html b/myproject/customers/templates/customers/customer_detail.html index dcb50d1..7b3ecb5 100644 --- a/myproject/customers/templates/customers/customer_detail.html +++ b/myproject/customers/templates/customers/customer_detail.html @@ -46,9 +46,11 @@ Баланс кошелька: {% if customer.wallet_balance > 0 %} - {{ customer.wallet_balance|floatformat:2 }} руб. + {{ customer.wallet_balance|floatformat:2 }} руб. + {% elif customer.wallet_balance == 0 %} + {{ customer.wallet_balance|floatformat:2 }} руб. {% else %} - {{ customer.wallet_balance|floatformat:2 }} руб. + {{ customer.wallet_balance|floatformat:2 }} руб. {% endif %} @@ -79,6 +81,211 @@ + + +
+
+
+
История кошелька (последние 20)
+ {{ wallet_transactions|length }} +
+
+ {% if wallet_transactions %} +
+ + + + + + + + + + + + + {% for transaction in wallet_transactions %} + + + + + + + + + {% endfor %} + +
ДатаТипСуммаОписаниеЗаказСоздал
{{ transaction.created_at|date:"d.m.Y H:i" }} + {% if transaction.transaction_type == 'deposit' %} + Пополнение + {% elif transaction.transaction_type == 'spend' %} + Списание + {% else %} + Корректировка + {% endif %} + + {% if transaction.transaction_type == 'deposit' or transaction.transaction_type == 'adjustment' and transaction.amount > 0 %} + +{{ transaction.amount|floatformat:2 }} руб. + {% else %} + -{{ transaction.amount|floatformat:2 }} руб. + {% endif %} + {{ transaction.description|default:"-" }} + {% if transaction.order %} + + #{{ transaction.order.order_number }} + + {% else %} + - + {% endif %} + {{ transaction.created_by.username|default:"-" }}
+
+ {% else %} +

История транзакций пуста.

+ {% endif %} +
+
+
+ + +
+
+
+
История заказов
+
+ {{ orders_page.paginator.count }} + + Новый заказ + +
+
+
+ {% if orders_page %} +
+ + + + + + + + + + + + + + + + {% for order in orders_page %} + + + + + + + + + + + + {% endfor %} + +
Дата созданияДата доставкиСтатусОплатаСуммаОплаченоОстатокДействия
#{{ order.order_number }}{{ order.created_at|date:"d.m.Y H:i" }} + {% if order.delivery_date %} + {{ order.delivery_date|date:"d.m.Y" }} + {% if order.delivery_time %} +
{{ order.delivery_time }} + {% endif %} + {% if order.is_delivery %} +
Доставка + {% else %} +
Самовывоз + {% endif %} + {% else %} + Не указана + {% endif %} +
+ {% if order.status == 'draft' %} + Черновик + {% elif order.status == 'pending' %} + Ожидает + {% elif order.status == 'in_production' %} + В производстве + {% elif order.status == 'ready' %} + Готов + {% elif order.status == 'delivered' %} + Доставлен + {% elif order.status == 'cancelled' %} + Отменён + {% else %} + {{ order.get_status_display }} + {% endif %} + + {% if order.payment_status == 'paid' %} + Оплачено + {% elif order.payment_status == 'partial' %} + Частично + {% else %} + Не оплачено + {% endif %} + {{ order.total_amount|floatformat:2 }} руб. + {% if order.amount_paid > 0 %} + {{ order.amount_paid|floatformat:2 }} руб. + {% else %} + 0.00 руб. + {% endif %} + + {% if order.amount_due > 0 %} + {{ order.amount_due|floatformat:2 }} руб. + {% else %} + 0.00 руб. + {% endif %} + + + + + + + +
+
+ + + {% if orders_page.has_other_pages %} + + {% endif %} + {% else %} +

У клиента пока нет заказов.

+ {% endif %} +
+
+
{% endblock %} \ No newline at end of file diff --git a/myproject/customers/views.py b/myproject/customers/views.py index f487ca8..b271983 100644 --- a/myproject/customers/views.py +++ b/myproject/customers/views.py @@ -89,10 +89,24 @@ def customer_detail(request, pk): active_orders = customer.orders.exclude(payment_status='paid') total_debt = sum(order.amount_due for order in active_orders) + # История транзакций кошелька (последние 20) + from .models import WalletTransaction + wallet_transactions = WalletTransaction.objects.filter( + customer=customer + ).select_related('order', 'created_by').order_by('-created_at')[:20] + + # История заказов с пагинацией + orders_list = customer.orders.all().order_by('-created_at') + paginator = Paginator(orders_list, 10) # 10 заказов на страницу + page_number = request.GET.get('page') + orders_page = paginator.get_page(page_number) + context = { 'customer': customer, 'total_debt': total_debt, 'active_orders_count': active_orders.count(), + 'wallet_transactions': wallet_transactions, + 'orders_page': orders_page, } return render(request, 'customers/customer_detail.html', context) diff --git a/myproject/orders/forms.py b/myproject/orders/forms.py index 102a4fb..5f844de 100644 --- a/myproject/orders/forms.py +++ b/myproject/orders/forms.py @@ -5,6 +5,7 @@ from .models import Order, OrderItem, Payment, Address, OrderStatus from customers.models import Customer from inventory.models import Warehouse from products.models import Product, ProductKit +from decimal import Decimal class OrderForm(forms.ModelForm): @@ -481,6 +482,44 @@ class PaymentForm(forms.ModelForm): # Делаем notes опциональным self.fields['notes'].required = False + def clean(self): + """Валидация платежа, особенно для оплаты из кошелька""" + cleaned = super().clean() + method = cleaned.get('payment_method') + amount = cleaned.get('amount') + order = getattr(self.instance, 'order', None) + + # Пустые формы допустимы при удалении + if not method and not amount: + return cleaned + + # Базовые проверки + if amount is None or amount <= 0: + self.add_error('amount', 'Введите сумму больше 0.') + + if not order: + raise forms.ValidationError('Заказ не найден для платежа.') + + # Проверка для оплаты из кошелька + if method and getattr(method, 'code', None) == 'account_balance': + wallet_balance = order.customer.wallet_balance if order.customer else Decimal('0') + amount_due = max(order.total_amount - order.amount_paid, Decimal('0')) + + if wallet_balance <= 0: + self.add_error('payment_method', 'Недостаточно средств в кошельке клиента (баланс 0).') + + if amount and amount > wallet_balance: + self.add_error('amount', f'Недостаточно средств в кошельке. Доступно {wallet_balance} руб.') + + if amount and amount > amount_due: + self.add_error('amount', f'Сумма превышает остаток к оплате ({amount_due} руб.)') + + if self.errors: + # Общее сообщение для блока формы + raise forms.ValidationError('Проверьте поля оплаты из кошелька.') + + return cleaned + # Formset для множественных платежей PaymentFormSet = inlineformset_factory( diff --git a/myproject/orders/models/payment.py b/myproject/orders/models/payment.py index 4020d86..18fa37e 100644 --- a/myproject/orders/models/payment.py +++ b/myproject/orders/models/payment.py @@ -1,5 +1,8 @@ from django.db import models from accounts.models import CustomUser +from decimal import Decimal +from django.db import transaction +from django.core.exceptions import ValidationError class PaymentMethod(models.Model): @@ -137,16 +140,41 @@ class Payment(models.Model): return f"Платеж {self.amount} руб. по заказу #{self.order.order_number}" def save(self, *args, **kwargs): - """При сохранении платежа обновляем сумму оплаты в заказе""" - super().save(*args, **kwargs) - # Пересчитываем общую сумму оплаты в заказе - self.order.amount_paid = sum(p.amount for p in self.order.payments.all()) - self.order.update_payment_status() + """При сохранении платежа обновляем сумму оплаты в заказе и обрабатываем кошелёк/переплаты""" + is_new = self.pk is None + with transaction.atomic(): + super().save(*args, **kwargs) - # Нормализация переплаты: лишнее в кошелёк, amount_paid = total_amount - try: - from customers.services.wallet_service import WalletService - WalletService.add_overpayment(self.order, self.created_by) - except Exception: - # Если обработка переплаты не удалась, продолжаем без ошибок - pass + # Пересчитываем общую сумму оплаты в заказе + self.order.amount_paid = sum(p.amount for p in self.order.payments.all()) + self.order.update_payment_status() + + # Списание из кошелька при новом платеже методом 'account_balance' + if is_new and self.payment_method.code == 'account_balance': + from customers.models import Customer, WalletTransaction + # Блокируем запись клиента + customer = Customer.objects.select_for_update().get(pk=self.order.customer_id) + if customer.wallet_balance < self.amount: + raise ValidationError(f'Недостаточно средств в кошельке (доступно {customer.wallet_balance} руб.)') + + # Списываем и округляем до 2 знаков + customer.wallet_balance = (customer.wallet_balance - self.amount).quantize(Decimal('0.01')) + customer.save(update_fields=['wallet_balance']) + + # Пишем историю + WalletTransaction.objects.create( + customer=customer, + amount=self.amount, + transaction_type='spend', + order=self.order, + description=f'Оплата из кошелька по заказу #{self.order.order_number}', + created_by=self.created_by + ) + + # Нормализация переплаты: лишнее в кошелёк, amount_paid = total_amount + try: + from customers.services.wallet_service import WalletService + WalletService.add_overpayment(self.order, self.created_by) + except Exception: + # Продолжаем, даже если нормализация переплаты не удалась + pass diff --git a/myproject/orders/templates/orders/order_form.html b/myproject/orders/templates/orders/order_form.html index 142c1f8..eac4271 100644 --- a/myproject/orders/templates/orders/order_form.html +++ b/myproject/orders/templates/orders/order_form.html @@ -126,7 +126,15 @@ - {{ form.customer }} + {% if preselected_customer %} + + {% else %} + {{ form.customer }} + {% endif %} {% if form.customer.errors %}
{{ form.customer.errors }}
{% endif %} @@ -565,11 +573,43 @@
Оплата
- +
+ + {{ order.get_payment_status_display }} + + +
+ + + {% if order.customer %} +
+
+ Кошелёк клиента: + {% if order.customer.wallet_balance > 0 %} + {{ order.customer.wallet_balance|floatformat:2 }} руб. + {% else %} + 0.00 руб. + {% endif %} + Остаток к оплате: {{ order.amount_due|floatformat:2 }} руб. +
+ {% if order.customer.wallet_balance > 0 and order.amount_due > 0 %} +
+ +
+ + +
+
+ {% endif %} +
+ {% endif %} + {{ payment_formset.management_form }} @@ -639,6 +679,9 @@
@@ -675,6 +718,98 @@ + +
Дополнительно
@@ -781,6 +916,14 @@ function initCustomerSelect2() { console.log('Значение восстановлено:', $customerSelect.val()); } + // Уведомляем draft-creator.js что Select2 готов и есть предзаполненное значение + if (currentValue && window.DraftCreator) { + console.log('7. Уведомляем DraftCreator о предзаполненном клиенте'); + setTimeout(function() { + window.DraftCreator.triggerDraftCreation(); + }, 100); + } + // Слушаем события $customerSelect.on('select2:open', function(e) { console.log('7. Dropdown открыт'); diff --git a/myproject/orders/views.py b/myproject/orders/views.py index 2388fcc..9565360 100644 --- a/myproject/orders/views.py +++ b/myproject/orders/views.py @@ -108,7 +108,19 @@ def order_create(request): else: messages.error(request, 'Пожалуйста, исправьте ошибки в форме.') else: - form = OrderForm() + # Предзаполнение клиента из GET параметра + initial_data = {} + preselected_customer = None + customer_id = request.GET.get('customer') + if customer_id: + try: + from customers.models import Customer + preselected_customer = Customer.objects.get(pk=customer_id) + initial_data['customer'] = preselected_customer.pk + except (Customer.DoesNotExist, ValueError): + pass + + form = OrderForm(initial=initial_data) formset = OrderItemFormSet() payment_formset = PaymentFormSet() @@ -116,6 +128,7 @@ def order_create(request): 'form': form, 'formset': formset, 'payment_formset': payment_formset, + 'preselected_customer': preselected_customer, 'title': 'Создание заказа', 'button_text': 'Создать заказ', }