Создан файл orders/tests/test_payment_methods.py с комплексными тестами:
1. PaymentMethodCreationTest (6 тестов)
- Проверка создания всех 5 способов оплаты через команду
- Проверка системных флагов и активности
- Проверка правильности порядка сортировки
- Проверка идемпотентности команды
- Критический тест наличия account_balance
2. PaymentMethodMultiTenantTest (2 теста)
- Проверка изоляции данных между тенантами
- Проверка кастомных способов оплаты в разных тенантах
3. PaymentMethodTransactionTest (7 тестов)
- Проверка связи PaymentMethod.transactions
- Проверка создания транзакций
- Проверка изоляции транзакций по способам оплаты
- Проверка защиты от удаления (PROTECT)
- Критический тест использования account_balance
- Исправление бага obj.payments → obj.transactions
4. PaymentMethodOrderingTest (2 теста)
- Проверка сортировки по полю order
- Проверка что account_balance первый (order=0)
Особенности тестирования:
- Использование TenantTestCase для изоляции тенантов
- Использование TransactionTestCase для мультитенантных тестов
- Ручное создание/удаление схем для безопасности
- Проверка изоляции данных между схемами
Результат: 15 тестов, все прошли успешно ✓
Проблема #1: Дублирование кода способов оплаты
- В tenants/admin.py был полный список способов оплаты (45 строк)
- В orders/management/commands/create_payment_methods.py был другой список
- При создании тенанта отсутствовал способ 'account_balance'
- Нарушение DRY принципа
Решение: Single Source of Truth
- Единственный источник истины: команда create_payment_methods
- В tenants/admin.py заменено дублирование на call_command()
- Удалено 45 строк дублирующего кода
- Теперь все тенанты получают одинаковый полный список
Проблема #2: AttributeError в админке PaymentMethod
- obj.payments.count() вызывал ошибку
- В модели Transaction связь называется 'transactions', а не 'payments'
Решение: Исправлено в orders/admin.py
- obj.payments → obj.transactions (2 места)
- Админка PaymentMethod теперь работает корректно
Для тенанта buba:
- Создан скрипт add_payment_methods_to_buba.py
- Добавлен недостающий способ оплаты 'С баланса счёта'
Изменённые файлы:
- myproject/tenants/admin.py - вызов команды вместо дублирования
- myproject/orders/admin.py - исправлено на transactions
- add_payment_methods_to_buba.py - скрипт для существующих тенантов
Проблема #1: Резервы не создавались при создании заказа
- Order сохранялся БЕЗ items → сигнал reserve_stock_on_order_create
не мог создать резервы (items.all() был пустой)
- OrderItem создавались ПОСЛЕ, но сигнал уже отработал
Решение #1: Создание резервов через сигнал OrderItem
- Доработан сигнал update_reservation_on_item_change
- Убрано раннее возвращение для created=True
- Теперь резервы создаются при добавлении OrderItem (любым способом)
- Работает для всех сценариев:
* Создание заказа с товарами
* Добавление товаров при редактировании
* Изменение количества
Проблема #2: Риск расхождений при удалении заказа
- Сигнал pre_delete освобождал резервы ДО удаления
- Если удаление падало с ошибкой → резервы освобождены, Order не удалён
- Возникало расхождение данных
Решение #2: transaction.on_commit для освобождения резервов
- Добавлен @transaction.atomic к сигналу release_stock_on_order_delete
- Резервы получаются ДО удаления через list()
- Освобождение происходит через transaction.on_commit()
- Резервы освобождаются ТОЛЬКО если удаление успешно
- Гарантия целостности данных
Изменённые файлы:
- myproject/inventory/signals.py - оба сигнала исправлены
- RESERVATION_FIX.md - полная документация
Проблема:
- При изменении статуса заказа на 'Выполнен' товар списывался дважды
- Заказ на 10 шт создавал Sale на 10 шт, но со склада уходило 20 шт
Найдено ДВЕ причины:
1. Повторное обновление резервов через .save() (inventory/signals.py)
- Резервы обновлялись через res.save() каждый раз при сохранении заказа
- Это вызывало сигнал update_stock_on_reservation_change
- При повторном сохранении заказа происходило двойное срабатывание
Решение:
- Проверка дубликатов ПЕРЕД обновлением резервов
- Замена .save() на .update() для массового обновления без вызова сигналов
- Ручное обновление Stock после .update()
2. Двойное FIFO-списание (inventory/services/sale_processor.py)
- Sale создавалась с processed=False
- Сигнал process_sale_fifo срабатывал и списывал товар (1-й раз)
- Затем SaleProcessor.create_sale() тоже списывал товар (2-й раз)
Решение:
- Sale создаётся сразу с processed=True
- Сигнал не срабатывает, списание только в сервисе
Дополнительно:
- Ограничен выбор статусов при создании заказа только промежуточными
- Статус 'Черновик' установлен по умолчанию
- Убран пустой выбор '-------' из поля статуса
Изменённые файлы:
- myproject/orders/forms.py - настройки статусов для формы заказа
- myproject/inventory/signals.py - исправление сигнала create_sale_on_order_completion
- myproject/inventory/services/sale_processor.py - исправление create_sale
- myproject/test_order_status_default.py - обновлён тест
- DOUBLE_SALE_FIX.md - документация по исправлению
Проблема: Резервы не обновлялись в статус 'converted_to_sale' если Sale уже существовали,
что приводило к отображению завершенных заказов на странице активных резервов.
Решение: Переместил обновление резервов ПЕРЕД проверкой существования Sale.
Теперь резервы всегда обновляются при переходе заказа в статус 'completed',
независимо от того, были ли уже созданы записи Sale.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Проблема: При создании заказа instance.status может быть None,
что вызывало AttributeError при попытке доступа к .code
Решение: Добавлена проверка 'not instance.status' перед
обращением к instance.status.code
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Проблема: Сигнал post_save срабатывает несколько раз,
создавая дубликаты Sale для одного заказа.
Решения:
1. Добавлена проверка Sale.objects.filter(order=instance).exists()
перед созданием продаж (inventory/signals.py:74-75)
2. Создана management команда fix_duplicate_sales для исправления
существующих дубликатов
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Проблема: Order.status - это объект OrderStatus, а не строка.
Сравнение instance.status != 'completed' всегда возвращало True.
Решение: Сравниваем instance.status.code != 'completed'
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Изменения:
- Сигнал create_sale_on_order_shipment переименован в create_sale_on_order_completion
- Списание товаров (создание Sale) теперь происходит при статусе 'completed' вместо 'in_delivery'
- Исправлен выбор склада: используется Order.pickup_warehouse, если задан
- Та же логика применена к резервированию товаров
Обоснование для цветочного бизнеса:
- Букет может вернуться в магазин (клиента нет дома, перенос доставки)
- Товар физически находится в магазине до момента доставки
- Резерв показывает что товар занят - этого достаточно для промежуточных статусов
- Простота: списываем только когда ТОЧНО продали
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Проблема:
- calculate_total() пытался автоматически обрабатывать переплату
- Это приводило к дублированию логики и сложной отладке
- Нарушался принцип единственной ответственности
Решение:
- Удалена автоматическая обработка переплаты из Order.calculate_total()
- Теперь calculate_total() ТОЛЬКО считает сумму - всё
- Переплата обрабатывается ТОЛЬКО в TransactionService при создании платежей/возвратов
- Добавлено предупреждение в UI о переплате с инструкцией
Как работает теперь:
1. При оплате - TransactionService автоматически вызывает add_overpayment()
2. При изменении товаров - calculate_total() только пересчитывает сумму
3. Если появилась переплата - оператор видит предупреждение
4. Оператор вручную создаёт возврат в кошелёк через форму
Преимущества:
- Одно место ответственности за переплаты
- Прозрачность для оператора
- Нет скрытых автоматизмов
- Легко обслуживать и отлаживать
- Стандартный подход для e-commerce
Проблема:
- add_overpayment вызывался дважды:
1. При оплате 300 руб (заказ 150) → +150 в кошелёк
2. При изменении суммы до 100 → +200 в кошелёк
- Итого: 350 руб вместо правильных 200 руб
Причина:
- calculate_total() вызывал add_overpayment при любой переплате
- Не учитывалось, что переплата уже была обработана при оплате
Решение:
- Сохраняем old_total перед пересчётом
- Вызываем add_overpayment ТОЛЬКО если:
- old_total > 0 (заказ уже существовал)
- total_amount < old_total (сумма УМЕНЬШИЛАСЬ)
- amount_paid > total_amount (есть переплата)
- Это предотвращает двойную обработку при первичной оплате
Теперь переплата обрабатывается корректно только при изменении суммы заказа
Проблема:
- Создан заказ на 150 руб, оплачено 300 руб → 150 руб в кошелёк
- Изменены товары, сумма стала 100 руб
- amount_paid остался 300, total_amount стал 100
- Новая переплата 200 руб НЕ переносилась в кошелёк автоматически
Решение:
- В Order.calculate_total() добавлена проверка переплаты после пересчёта суммы
- Если amount_paid > total_amount, вызывается WalletService.add_overpayment()
- Излишек автоматически переносится в кошелёк, amount_paid нормализуется до total_amount
- Создаётся WalletTransaction для аудита
Теперь при уменьшении суммы заказа переплата корректно возвращается клиенту
- Устранён N+1 для статусов заказов: добавлен select_related('status')
- Расчёт total_debt перенесён на сторону БД через aggregate с Greatest/Coalesce
- Избежана загрузка всех активных заказов в память для подсчёта долга
- Количество активных заказов теперь считается через count() без загрузки данных
- Ожидаемый эффект: минус 10+ запросов на страницу, быстрее рендер при большом количестве заказов
- Исправлен баг отображения статуса: теперь сравнение с order.status.code вместо order.status
- Добавлена обработка отсутствующего статуса (показывает 'Без статуса')
- Пагинация истории заказов: добавлен якорь #ordersHistoryCollapse ко всем ссылкам
- Автооткрытие collapse при переходе по пагинации через JavaScript
- Плавная прокрутка к секции истории после раскрытия collapse (событие shown.bs.collapse)
- Пользователь остаётся в секции истории заказов при переходе между страницами
- История кошелька и история заказов теперь под collapse (свёрнуты по умолчанию)
- Кликабельные заголовки с иконкой chevron для раскрытия
- Badge с количеством элементов в заголовке
- Кнопка 'Новый заказ' доступна в свёрнутом состоянии (event.stopPropagation)
- Компактный и удобный интерфейс для работы с большими списками
- Пополнение и Списание размещены рядом (col-md-6 каждая)
- Упрощены тексты заголовков, лейблов и кнопок для компактности
- Фиксированная высота для подсказок — обеспечена симметрия полей
- Убран разделитель между формами
- Короткие placeholder'ы в полях ввода
- Информация о клиенте — левая колонка (col-md-6)
- Операции с кошельком — правая колонка (col-md-6)
- Баланс кошелька перенесён из таблицы в заголовок блока операций
- История кошелька и история заказов остаются в полную ширину
- Компактный вертикальный layout форм в правой колонке
- Добавлены view wallet_deposit и wallet_withdraw с защитой (login_required, is_staff, CSRF)
- Новые маршруты: /customers/<pk>/wallet/deposit/ и /customers/<pk>/wallet/withdraw/
- UI на странице клиента: две симметричные формы для пополнения и списания баланса
- Пополнение: произвольная сумма с обязательным описанием (подарки, компенсации)
- Возврат/списание: с ограничением макс. суммы = текущий баланс, обязательное описание
- Все операции логируются в WalletTransaction с типом 'adjustment'
- Защита от операций с системным клиентом
- Компактный симметричный дизайн форм с фиксированной высотой подсказок
Изменения в UI (order_form.html):
- Добавлен data-code к опциям способов оплаты для идентификации метода кошелька
- ID для селекта способа оплаты (payment-method-select) и поля суммы (payment-amount-input)
- Динамическое ограничение max на поле суммы платежа при выборе кошелька
- Подсказка 'Макс: X руб.' отображается только для оплаты кошельком
- Для внешних методов (карта, наличные) ограничение отсутствует — переплата допустима
Логика JS:
- При выборе метода с code == 'account_balance' устанавливается max = order.amount_due
- Для остальных методов max удаляется — оператор может внести сумму больше остатка
- Переплата по внешним методам корректно зачисляется в кошелёк через WalletService.add_overpayment
Серверная защита (transaction_service.py):
- В TransactionService.create_payment добавлена проверка:
если payment_method.code == 'account_balance' и amount > order.amount_due — ValidationError
- Сообщение: 'Сумма оплаты из кошелька (X руб.) не может превышать остаток к оплате (Y руб.)'
- Защита от обхода UI через API или прямой вызов
Улучшение отображения (order_form.html, order_detail.html):
- Для возврата в кошелёк (transaction_type == 'refund' и code == 'account_balance') показываем 'на баланс счёта' вместо названия метода
- История становится понятнее: '−750,00 руб. Возврат 29.11.2025 на баланс счёта'
Сценарий:
- Кошелёк клиента 500 руб., заказ 65 руб.
- Оператор выбирает оплату из кошелька — поле суммы ограничено 65 руб.
- Попытка ввести 500 заблокирована UI и серверной валидацией
- Для внешней оплаты (карта онлайн) можно внести 500 руб. — остаток 435 автоматически зачислится в кошелёк как переплата
Цель:
- Исключить путаницу в истории транзакций при оплате кошельком
- Разграничить поведение: кошелёк строго ограничен, внешние методы допускают переплату
- Обеспечить прозрачность движения средств для операторов
Изменения:
- Добавлена пометка '(кошелёк клиента)' к методу оплаты с кодом account_balance в селектах платежа и возврата
- Обновлён текст предупреждения о возврате: теперь явно указано, что зачисление в кошелёк происходит только при выборе метода 'кошелёк клиента'
- Для всех остальных методов (наличные, карта и т.п.) возврат — это информационная метка для истории, без фактического движения средств
Цель:
- Устранить путаницу операторов относительно поведения возвратов
- Чётко разделить возврат клиенту (внешними способами) и зачисление в кошелёк (только для account_balance)
- UI теперь соответствует фактической серверной логике в Transaction.save()
Защита от переплаты:
- Серверная валидация в TransactionService.create_refund проверяет amount <= order.amount_paid
- UI ограничение max на поле ввода суммы возврата
- ValidationError с понятным сообщением при попытке превысить лимит
- Move order form scope to left column only to avoid nested forms
- Place payment/refund forms in right column within same grid row
- Remove transaction delete button - use refunds instead for audit trail
- Simplify transaction history table: show only Date, Payment Method, Amount, and Created By
- Fix form submit buttons to use form attribute for proper association
- Improve visual alignment of two-column layout without empty gaps
This ensures valid HTML (no nested forms), clean financial audit history, and better UX with aligned columns.
- Разделен экран на две колонки: заказ слева, оплата справа
- Форма оплаты вынесена за пределы основной формы заказа (устранена проблема вложенных форм)
- Исправлен метод calculate_total() для сохранения итоговой суммы в БД
- Добавлена модель Transaction для учета платежей и возвратов
- Добавлена модель PaymentMethod для методов оплаты
- Удалена старая модель Payment, заменена на Transaction
- Добавлен TransactionService для управления транзакциями
- Обновлен интерфейс форм оплаты для правой колонки
- Кнопка 'Сохранить изменения' теперь работает корректно
ПРОБЛЕМА:
Использование PaymentFormSet для платежей было НЕПРАВИЛЬНЫМ подходом:
1. Платежи = финансовые транзакции (не должны редактироваться inline)
2. Формы валидировали существующие платежи как новые
3. Сложная логика с formset management forms
4. Конфликты валидации кошелька
РЕШЕНИЕ (Django Best Practices):
Разделили управление платежами на отдельные операции:
АРХИТЕКТУРА:
`
POST /orders/111/payments/add/ # Добавить платеж
POST /orders/111/payments/123/delete/ # Удалить платеж
`
ПРЕИМУЩЕСТВА:
✅ Чистая архитектура (separation of concerns)
✅ Платежи = неизменяемые транзакции
✅ Простая валидация (только для новых)
✅ Легко тестировать
✅ API-ready структура
ИЗМЕНЕНИЯ:
1. orders/views.py:
- Убран PaymentFormSet из order_create и order_update
- Добавлен payment_add(request, order_number)
- Добавлен payment_delete(request, order_number, payment_id)
- Используется простой PaymentForm вместо formset
- Payment.save() автоматически обрабатывает:
* Списание из кошелька
* Обработку переплаты
* Обновление amount_paid
2. orders/urls.py:
- Добавлены URL patterns для payment-add и payment-delete
- Структура: /orders/<number>/payments/add|<id>/delete/
3. orders/templates/orders/order_form.html:
- Убран PaymentFormSet и все его скрипты (~265 строк)
- Простая HTML форма для добавления платежа
- Существующие платежи: read-only список с кнопками удаления
- Каждое удаление = отдельный POST запрос
- Для создания: показываем предупреждение вместо формы
4. orders/templatetags/orders_tags.py (NEW):
- Template tag get_payment_methods
- Загружает активные способы оплаты
- Использование: {% get_payment_methods as payment_methods %}
РЕЗУЛЬТАТ:
- Код: -191 строка
- Логика: простая и понятная
- Архитектура: правильная (как в учебнике)
- Платежи: только add/delete (без edit)
- Валидация: работает корректно
- UX: чище и понятнее
ПРОБЛЕМА:
После предыдущего коммита кнопка сохранения заказа перестала работать.
Клик на кнопку не приводил к отправке формы - ноль реакции.
ПРИЧИНА:
Вложенная форма удаления платежа внутри основной формы order-form.
Вложенные формы недопустимы в HTML и браузер неправильно обрабатывает
submit-события.
РЕШЕНИЕ:
Заменил вложенную форму на JavaScript обработчик:
- Кнопка удаления теперь type=button (не submit)
- Добавлены data-атрибуты: payment-id, payment-name, payment-amount
- JavaScript создает временную форму для POST-запроса с delete_payment_id
- Форма отправляется программно через form.submit()
ИЗМЕНЕНИЯ:
- Заменена форма на button для удаления платежей
- Добавлен JavaScript обработчик .delete-existing-payment-btn
- Подтверждение удаления с именем и суммой платежа
РЕЗУЛЬТАТ:
✅ Кнопка сохранения заказа работает
✅ Удаление существующих платежей работает
✅ Нет вложенных форм (валидный HTML)
ПРОБЛЕМА:
При редактировании заказа с уже существующими платежами из кошелька,
formset пытался валидировать ВСЕ платежи как новые, включая уже
проведенные. Это вызывало ошибки валидации кошелька, даже когда
пользователь просто хотел добавить новый платеж другим методом.
РЕШЕНИЕ:
Разделили отображение платежей на две части:
1. УЖЕ ПРОВЕДЕННЫЕ ПЛАТЕЖИ (информационный блок):
- Показываются в виде read-only карточек (bg-light)
- Не проходят через formset валидацию
- Можно удалить через отдельную форму с POST-запросом
- Содержат: способ оплаты, сумму, примечания, кнопку удаления
2. НОВЫЕ ПЛАТЕЖИ (formset):
- Добавляются через кнопку 'Добавить платеж'
- Проходят валидацию только для новых записей
- Контейнер изначально пустой (#payments-container)
ИЗМЕНЕНИЯ:
orders/templates/orders/order_form.html:
- Добавлен блок 'Проведенные платежи' с информационным отображением
- Каждый существующий платеж с формой удаления (delete_payment_id)
- Контейнер для новых платежей теперь пустой при загрузке
- Обновлен calculatePaymentsTotal(): считает существующие + новые
- Убраны обработчики для несуществующих элементов formset
- Итоговая сумма инициализируется из order.amount_paid
orders/views.py (order_update):
- Добавлена обработка delete_payment_id из POST
- При удалении платежа из кошелька - возврат средств через WalletService
- Пересчет amount_paid после удаления
- Редирект обратно в форму после удаления
РЕЗУЛЬТАТ:
✅ Существующие платежи не валидируются повторно
✅ Можно свободно добавлять новые платежи любым методом
✅ Удаление существующих платежей работает корректно
✅ Возврат в кошелек при удалении платежа 'account_balance'
✅ Правильный подсчет итоговой суммы (существующие + новые)
ПРОБЛЕМА:
При оплате заказа с переплатой (например, 15000 руб за заказ 7770 руб),
сдача возвращалась в кошелек клиента дважды:
- 1 раз: 7230 руб (правильно)
- 2 раз: 7230 руб (дубль!)
- ИТОГО: 14460 руб вместо 7230 руб
ПРИЧИНА:
Обработка переплаты вызывалась в двух местах:
1. Payment.save() → вызывал WalletService.add_overpayment() ✓
2. order_create/order_update в views.py → еще раз вызывал add_overpayment() ✗
РЕШЕНИЕ:
Убраны дублирующие вызовы WalletService.add_overpayment() из views.py.
Теперь переплата обрабатывается ТОЛЬКО в Payment.save() - это правильное
место, т.к. переплата появляется именно при сохранении нового платежа.
ИЗМЕНЕНИЯ:
- orders/views.py (order_create): убран вызов add_overpayment
- orders/views.py (order_update): убран вызов add_overpayment
Теперь при переплате сдача возвращается ровно 1 раз.
Убрано поле скидки из системы для последующей реализации полноценной системы скидок.
Изменения:
- Удалено поле discount_amount из модели Order
- Убрано из формы OrderForm
- Удалено из шаблонов order_form.html и order_detail.html
- Убрано из админки OrderAdmin
- Обновлен метод calculate_total() (без вычитания скидки)
В будущем будет создана отдельная модель Discount с промокодами, процентными скидками и автоматическими акциями.
ВАЖНО: После этого коммита нужно создать и применить миграцию:
python manage.py makemigrations orders -n remove_discount_amount
python manage.py migrate orders
- Добавлен префикс 'payments' для PaymentFormSet во всех представлениях
- Добавлен атрибут form='order-form' для динамически создаваемых полей платежей
- Убрано переопределение has_changed() в PaymentForm (использует стандартную логику Django)
- Автоматическая установка created_by для новых платежей
- Автоматический пересчёт payment_status при изменении суммы заказа
- Автоматическая обработка переплаты с возвратом в кошелёк клиента
- Убран весь отладочный код
Проблема: Платежи не сохранялись при создании/редактировании заказа.
Причины:
1. JavaScript функция addNewPayment() использовала неправильный метод
замены __prefix__. При clone().innerHTML.replace() атрибуты name
оставались с буквальным "__prefix__" вместо номера формы.
2. PaymentForm не переопределял has_changed(), из-за чего Django formset
считал заполненные формы "пустыми" и не сохранял их.
Исправления:
- order_form.html: Переписана addNewPayment() - теперь клонирует
template.content, конвертирует в HTML строку, делает replace,
и только потом парсит обратно в DOM элемент
- forms.py: Добавлен метод PaymentForm.has_changed() который правильно
определяет что форма заполнена если указан payment_method ИЛИ amount
- views.py: Добавлена отладочная информация для диагностики проблем
с formset (TODO: удалить после тестирования)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed PaymentForm.clean() validation that was preventing payments from
being saved on new orders. The validation required order to exist, but
during creation self.instance.order is None until formset is saved.
Changes:
- Removed hard requirement for order in PaymentForm.clean()
- Wallet balance checks now only run when order exists
- Empty payment forms still allowed (for deletion in formset)
- Basic amount validation maintained
This fixes the issue where payments wouldn't persist when creating
a new order, even though no validation errors were shown to user.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
The error occurred because:
1. OrderForm.save(commit=False) was calling reset_delivery_cost()
2. reset_delivery_cost() uses DeliveryCostCalculator which accesses order.items
3. But items don't exist yet when order is not saved to DB
Solution:
- OrderForm.save() now only calls reset_delivery_cost() when commit=True
- order_create() explicitly calls reset_delivery_cost() AFTER saving items
- This ensures items exist in DB before delivery cost calculation
Error was: 'Order' instance needs to have a primary key value before this relationship can be used.
- Removed autosave.js (665 lines) and draft-creator.js (441 lines)
- Removed draft_service.py (~500 lines) and DraftOrderService
- Removed AJAX endpoints: autosave and create-draft
- Updated order_create() to add is_create_page flag
- Updated order_update() to finalize drafts without DraftOrderService
- Added get_new_status() method to OrderStatusService
- Updated order_form.html:
- Removed old JS includes
- Added beforeunload warning for unsaved data
- Updated buttons: separate buttons for create/draft/finalize
- Total code reduction: ~1600 lines (92% removed)
New workflow:
- /orders/create/ - user fills form, chooses button
- /orders/<id>/edit/ - simple editing without autosave
- beforeunload warning when leaving page (except on submit)