diff --git a/myproject/orders/forms.py b/myproject/orders/forms.py index 5f844de..ed3c454 100644 --- a/myproject/orders/forms.py +++ b/myproject/orders/forms.py @@ -143,6 +143,7 @@ class OrderForm(forms.ModelForm): 'class': 'form-select select2', 'data-placeholder': 'Выберите адрес доставки' }) + # Адрес доставки не обязателен при редактировании (создаётся из отдельных полей) self.fields['delivery_address'].required = False self.fields['pickup_warehouse'].widget.attrs.update({ @@ -221,9 +222,16 @@ class OrderForm(forms.ModelForm): class OrderItemForm(forms.ModelForm): """Форма для позиции заказа""" + # Элегантно переопределяем поле формы, чтобы парсить '277,00' как Decimal + price = forms.CharField( + required=False, + widget=forms.TextInput(attrs={'class': 'form-control', 'step': '0.01', 'min': '0'}) + ) + class Meta: model = OrderItem fields = ['product', 'product_kit', 'quantity', 'price', 'is_custom_price'] + # ВАЖНО: НЕ включаем 'id' в fields - это предотвращает ошибку валидации widgets = { 'quantity': forms.NumberInput(attrs={'min': 1, 'value': 1}), # Скрываем поля product и product_kit - они будут заполняться через JS @@ -254,6 +262,17 @@ class OrderItemForm(forms.ModelForm): # Поле is_custom_price устанавливается через JS self.fields['is_custom_price'].required = False + def clean_price(self): + """Парсим цену с запятой или точкой""" + value = self.cleaned_data.get('price') + if value in (None, ''): + return None + value_str = str(value).strip().replace(',', '.') + try: + return Decimal(value_str) + except Exception: + raise forms.ValidationError('Введите число.') + def clean(self): """Валидация: должен быть выбран либо товар, либо комплект (не оба, не ни один)""" cleaned_data = super().clean() diff --git a/myproject/orders/models/order.py b/myproject/orders/models/order.py index 8636a4e..e9c887b 100644 --- a/myproject/orders/models/order.py +++ b/myproject/orders/models/order.py @@ -263,23 +263,18 @@ class Order(models.Model): """Валидация модели""" super().clean() - # Проверка: для доставки обязателен адрес - if self.is_delivery and not self.delivery_address: - raise ValidationError({ - 'delivery_address': 'Для доставки необходимо указать адрес доставки' - }) - # Проверка: для самовывоза обязателен склад if not self.is_delivery and not self.pickup_warehouse: raise ValidationError({ 'pickup_warehouse': 'Для самовывоза необходимо выбрать склад' }) - # Проверка: время окончания должно быть позже времени начала + # Проверка: время окончания должно быть позже или равно времени начала + # Равные времена означают точное время доставки (например, "к 13:00") if self.delivery_time_start and self.delivery_time_end: - if self.delivery_time_end <= self.delivery_time_start: + if self.delivery_time_end < self.delivery_time_start: raise ValidationError({ - 'delivery_time_end': 'Время окончания должно быть позже времени начала' + 'delivery_time_end': 'Время окончания не может быть раньше времени начала' }) def get_delivery_cost(self): @@ -384,5 +379,8 @@ class Order(models.Model): def delivery_time_window(self): """Временное окно доставки""" if self.delivery_time_start and self.delivery_time_end: + # Если времена равны - это точное время доставки + if self.delivery_time_start == self.delivery_time_end: + return f"к {self.delivery_time_start.strftime('%H:%M')}" return f"{self.delivery_time_start.strftime('%H:%M')} - {self.delivery_time_end.strftime('%H:%M')}" return "Время не указано" diff --git a/myproject/orders/templates/orders/order_form.html b/myproject/orders/templates/orders/order_form.html index eac4271..b6924a3 100644 --- a/myproject/orders/templates/orders/order_form.html +++ b/myproject/orders/templates/orders/order_form.html @@ -843,6 +843,90 @@ diff --git a/myproject/orders/templates/orders/order_list.html b/myproject/orders/templates/orders/order_list.html index 09aebd2..9c9bd3a 100644 --- a/myproject/orders/templates/orders/order_list.html +++ b/myproject/orders/templates/orders/order_list.html @@ -130,13 +130,23 @@ {% endif %} - {% if order.status %} - - {{ order.status.label|default:order.status.name }} +
+ + {% if order.status %} + {{ order.status.label|default:order.status.name }} + {% else %} + Не установлен + {% endif %} - {% else %} - Не установлен - {% endif %} + +
{{ order.total_amount }} руб. @@ -225,4 +235,79 @@ {% block extra_js %} + {% endblock %} diff --git a/myproject/orders/urls.py b/myproject/orders/urls.py index 1050862..9e202b7 100644 --- a/myproject/orders/urls.py +++ b/myproject/orders/urls.py @@ -19,6 +19,9 @@ urlpatterns = [ # Wallet payment path('/apply-wallet/', views.apply_wallet_payment, name='apply-wallet'), + # AJAX status update + path('api//set-status/', views.set_order_status, name='api-set-order-status'), + # Order Status Management URLs path('statuses/', views.order_status_list, name='status_list'), path('statuses/create/', views.order_status_create, name='status_create'), diff --git a/myproject/orders/views.py b/myproject/orders/views.py index 9565360..550940a 100644 --- a/myproject/orders/views.py +++ b/myproject/orders/views.py @@ -202,6 +202,20 @@ def order_update(request, pk): messages.success(request, f'Заказ #{order.order_number} успешно обновлен!') return redirect('orders:order-detail', pk=order.pk) else: + # Логируем ошибки для отладки + print("\n=== ОШИБКИ ВАЛИДАЦИИ ФОРМЫ ===") + if not form.is_valid(): + print(f"OrderForm errors: {form.errors}") + if not formset.is_valid(): + print(f"OrderItemFormSet errors: {formset.errors}") + print(f"OrderItemFormSet non_form_errors: {formset.non_form_errors()}") + for i, item_form in enumerate(formset): + if item_form.errors: + print(f" Item form {i} errors: {item_form.errors}") + if not payment_formset.is_valid(): + print(f"PaymentFormSet errors: {payment_formset.errors}") + print(f"PaymentFormSet non_form_errors: {payment_formset.non_form_errors()}") + print("=== КОНЕЦ ОШИБОК ===\n") messages.error(request, 'Пожалуйста, исправьте ошибки в форме.') else: form = OrderForm(instance=order) @@ -662,3 +676,43 @@ def apply_wallet_payment(request, pk): messages.error(request, f'Ошибка при оплате из кошелька: {str(e)}') return redirect('orders:order-detail', pk=pk) + + +@require_http_methods(["POST"]) +@login_required +def set_order_status(request, pk): + """ + Update order status via AJAX. + Accepts POST with 'status_id' (can be empty to clear). + Returns JSON with the resulting status info. + """ + try: + order = get_object_or_404(Order, pk=pk) + status_id = request.POST.get('status_id', '').strip() + + # Allow clearing status if empty + if status_id == '': + order.status = None + order.modified_by = request.user + order.save(update_fields=['status', 'modified_by', 'updated_at']) + return JsonResponse({'success': True, 'status': None}) + + try: + status = OrderStatus.objects.get(pk=status_id) + except OrderStatus.DoesNotExist: + return JsonResponse({'success': False, 'error': 'Status not found'}, status=404) + + order.status = status + order.modified_by = request.user + order.save(update_fields=['status', 'modified_by', 'updated_at']) + + return JsonResponse({ + 'success': True, + 'status': { + 'id': status.pk, + 'name': status.label or status.name, + 'color': status.color + } + }) + except Exception as e: + return JsonResponse({'success': False, 'error': f'Server error: {str(e)}'}, status=500)