fix: Исправлена работа черновиков заказов и добавлено автосохранение статуса

Проблема 1: Ошибка 500 при создании черновика заказа
- Поле status в модели Order является ForeignKey на OrderStatus
- В коде использовались строковые значения 'draft' и 'new' вместо объектов
- Это приводило к TypeError при создании/обновлении заказов

Решение:
- В DraftOrderService.create_draft: добавлен get_or_create для статуса 'draft'
- В DraftOrderService.finalize_draft: добавлен get_or_create для статуса 'new'
- В DraftOrderService.get_user_drafts: заменен фильтр status='draft' на status__code='draft'
- В DraftOrderService.delete_old_drafts: заменен фильтр status='draft' на status__code='draft'
- В cleanup_draft_orders.py: исправлен фильтр в режиме dry-run

Проблема 2: Отсутствие автосохранения при изменении статуса
- Поле status не отслеживалось в autosave.js
- При смене статуса заказ не сохранялся автоматически

Решение:
- Добавлено поле 'select[name="status"]' в список отслеживаемых полей
- Добавлен сбор значения статуса в функции collectFormData
- Добавлено 'status': 'orders.OrderStatus' в fk_fields для обработки на сервере

Дополнительно:
- Добавлено автосохранение полей адреса доставки (улица, дом, квартира и т.д.)
- Добавлено автосохранение полей получателя (имя, телефон)
- Добавлена автоматическая установка address_mode='new' при наличии адреса

Файлы:
- orders/services/draft_service.py
- orders/management/commands/cleanup_draft_orders.py
- orders/static/orders/js/autosave.js

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-14 23:00:24 +03:00
parent 8deef2fa75
commit d3ac875a0e
3 changed files with 101 additions and 5 deletions

View File

@@ -50,7 +50,7 @@ class Command(BaseCommand):
cutoff_date = timezone.now() - timedelta(days=days) cutoff_date = timezone.now() - timedelta(days=days)
old_drafts = Order.objects.filter( old_drafts = Order.objects.filter(
status='draft', status__code='draft',
last_autosave_at__lt=cutoff_date last_autosave_at__lt=cutoff_date
) )
count = old_drafts.count() count = old_drafts.count()

View File

@@ -39,9 +39,21 @@ class DraftOrderService:
data = data or {} data = data or {}
with transaction.atomic(): with transaction.atomic():
# Получаем или создаем статус 'draft'
from ..models import OrderStatus
draft_status, _ = OrderStatus.objects.get_or_create(
code='draft',
defaults={
'name': 'Черновик',
'label': 'Черновик',
'is_system': True,
'color': '#808080',
}
)
order = Order.objects.create( order = Order.objects.create(
customer=customer, customer=customer,
status='draft', status=draft_status,
modified_by=user, modified_by=user,
is_delivery=data.get('is_delivery', True), is_delivery=data.get('is_delivery', True),
delivery_address=data.get('delivery_address'), delivery_address=data.get('delivery_address'),
@@ -86,6 +98,7 @@ class DraftOrderService:
fk_fields = { fk_fields = {
'customer': 'customers.Customer', 'customer': 'customers.Customer',
'pickup_shop': 'shops.Shop', 'pickup_shop': 'shops.Shop',
'status': 'orders.OrderStatus',
} }
simple_fields = [ simple_fields = [
@@ -308,8 +321,20 @@ class DraftOrderService:
# Выполняем полную валидацию модели # Выполняем полную валидацию модели
order.full_clean() order.full_clean()
# Получаем или создаем статус 'new'
from ..models import OrderStatus
new_status, _ = OrderStatus.objects.get_or_create(
code='new',
defaults={
'name': 'Новый',
'label': 'Новый',
'is_system': True,
'color': '#0d6efd',
}
)
# Изменяем статус на 'new' # Изменяем статус на 'new'
order.status = 'new' order.status = new_status
order.modified_by = user order.modified_by = user
order.last_autosave_at = None # Очищаем, т.к. заказ больше не черновик order.last_autosave_at = None # Очищаем, т.к. заказ больше не черновик
order.save() order.save()
@@ -335,7 +360,7 @@ class DraftOrderService:
QuerySet: Черновики заказов QuerySet: Черновики заказов
""" """
drafts = Order.objects.filter( drafts = Order.objects.filter(
status='draft', status__code='draft',
modified_by=user modified_by=user
).select_related('customer', 'delivery_address', 'pickup_shop') ).select_related('customer', 'delivery_address', 'pickup_shop')
@@ -361,7 +386,7 @@ class DraftOrderService:
# Находим старые черновики # Находим старые черновики
old_drafts = Order.objects.filter( old_drafts = Order.objects.filter(
status='draft', status__code='draft',
last_autosave_at__lt=cutoff_date last_autosave_at__lt=cutoff_date
) )

View File

@@ -136,6 +136,7 @@
// Слушаем изменения в основных полях заказа // Слушаем изменения в основных полях заказа
const fieldsToWatch = [ const fieldsToWatch = [
'select[name="customer"]', 'select[name="customer"]',
'select[name="status"]',
'input[name="delivery_date"]', 'input[name="delivery_date"]',
'input[name="delivery_time_start"]', 'input[name="delivery_time_start"]',
'input[name="delivery_time_end"]', 'input[name="delivery_time_end"]',
@@ -147,6 +148,17 @@
'input[type="radio"]', 'input[type="radio"]',
'select[name="delivery_address"]', 'select[name="delivery_address"]',
'select[name="pickup_shop"]', 'select[name="pickup_shop"]',
// Поля адреса доставки
'input[name="address_street"]',
'input[name="address_building_number"]',
'input[name="address_apartment_number"]',
'input[name="address_entrance"]',
'input[name="address_floor"]',
'input[name="address_intercom_code"]',
'textarea[name="address_delivery_instructions"]',
// Поля получателя
'input[name="recipient_name"]',
'input[name="recipient_phone"]',
]; ];
fieldsToWatch.forEach(selector => { fieldsToWatch.forEach(selector => {
@@ -290,6 +302,11 @@
data.customer = parseInt(customerField.value); data.customer = parseInt(customerField.value);
} }
const statusField = form.querySelector('select[name="status"]');
if (statusField && statusField.value) {
data.status = parseInt(statusField.value);
}
const deliveryDateField = form.querySelector('input[name="delivery_date"]'); const deliveryDateField = form.querySelector('input[name="delivery_date"]');
if (deliveryDateField && deliveryDateField.value) { if (deliveryDateField && deliveryDateField.value) {
data.delivery_date = deliveryDateField.value; data.delivery_date = deliveryDateField.value;
@@ -352,6 +369,60 @@
data.pickup_shop = parseInt(pickupShopField.value); data.pickup_shop = parseInt(pickupShopField.value);
} }
// Поля адреса доставки (новая логика с прямым вводом)
const addressStreetField = form.querySelector('input[name="address_street"]');
if (addressStreetField && addressStreetField.value) {
data.address_street = addressStreetField.value;
// Указываем режим "новый адрес" если есть улица
data.address_mode = 'new';
}
const addressBuildingField = form.querySelector('input[name="address_building_number"]');
if (addressBuildingField && addressBuildingField.value) {
data.address_building_number = addressBuildingField.value;
}
const addressApartmentField = form.querySelector('input[name="address_apartment_number"]');
if (addressApartmentField && addressApartmentField.value) {
data.address_apartment_number = addressApartmentField.value;
}
const addressEntranceField = form.querySelector('input[name="address_entrance"]');
if (addressEntranceField && addressEntranceField.value) {
data.address_entrance = addressEntranceField.value;
}
const addressFloorField = form.querySelector('input[name="address_floor"]');
if (addressFloorField && addressFloorField.value) {
data.address_floor = addressFloorField.value;
}
const addressIntercomField = form.querySelector('input[name="address_intercom_code"]');
if (addressIntercomField && addressIntercomField.value) {
data.address_intercom_code = addressIntercomField.value;
}
const addressInstructionsField = form.querySelector('textarea[name="address_delivery_instructions"]');
if (addressInstructionsField && addressInstructionsField.value) {
data.address_delivery_instructions = addressInstructionsField.value;
}
const addressConfirmField = form.querySelector('input[name="address_confirm_with_recipient"]');
if (addressConfirmField) {
data.address_confirm_with_recipient = addressConfirmField.checked;
}
// Поля получателя
const recipientNameField = form.querySelector('input[name="recipient_name"]');
if (recipientNameField && recipientNameField.value) {
data.recipient_name = recipientNameField.value;
}
const recipientPhoneField = form.querySelector('input[name="recipient_phone"]');
if (recipientPhoneField && recipientPhoneField.value) {
data.recipient_phone = recipientPhoneField.value;
}
// Собираем позиции заказа // Собираем позиции заказа
data.items = collectOrderItems(); data.items = collectOrderItems();