- Изменена проверка с >= на > в Delivery.clean()
- Равные времена разрешены для POS-продаж (самовывоз в точное время)
- Обновлены сообщения об ошибках валидации
Add new models UnitOfMeasure and ProductSalesUnit to enable selling products in different units (e.g., bunches, kg). Update Product model with base_unit field and methods for unit conversions and availability. Extend Sale, Reservation, and OrderItem models with sales_unit fields and snapshots. Modify SaleProcessor to handle quantity conversions. Include admin interfaces for managing units. Add corresponding database migrations.
Основные изменения:
- Переход от денормализованного поля wallet_balance к вычисляемому балансу
- Баланс теперь вычисляется как SUM(signed_amount) транзакций
- Добавлено кеширование баланса для производительности (5 минут)
- Новая модель WalletTransaction с полем signed_amount (может быть +/-)
- WalletService для всех операций с кошельком (deposit, spend, adjustment)
- Защита от отрицательного баланса и race conditions через select_for_update
- Добавлен balance_after в каждую транзакцию для аудита
- Обновлены миграции для переноса данных из старой схемы
Улучшения безопасности:
- Атомарные транзакции для всех операций с балансом
- Блокировка строк при модификации баланса
- Валидация недостаточности средств
- Обязательное описание для корректировок баланса
UI/UX изменения:
- Обновлён вывод баланса кошелька в деталях клиента
- Добавлена история транзакций с типами и описаниями
- Цветовая индикация положительных транзакций (зелёный)
Техническая документация:
- Добавлены docstrings для всех методов WalletService
- Комментарии к критичным участкам кода
- Примеры использования в docstrings
- Implement `check_released_reservations_available` function to verify if items from released reservations are still available for re-sale when attempting to change a returned order's status
- Update `create_sale_on_order_completion` signal to use this check, allowing transitions to positive statuses only if items are available, otherwise blocking with ValidationError
- Wrap Order.save() in transaction.atomic() to ensure ValidationError in signals rolls back the save operation
- Add comprehensive tests for scenarios where items are available or used in other orders
- Update date carousel in order to always center on today's date and remove unnecessary saving logic
- Add test flag to Django Debug Toolbar settings
Closes#123 (assuming related issue)
- Заменено поле phone с CharField на PhoneNumberField для автоматической нормализации телефонов
- Убран регион BY, установлен region=None для универсальности (поддержка номеров разных стран)
- Добавлено поле notes для дополнительной информации о получателе (мессенджеры, соцсети и т.д.)
- Улучшена логика поиска существующих получателей:
* Использование нормализованного телефона из PhoneNumberField
* Регистронезависимый поиск по имени (name__iexact)
* Обновление notes при нахождении существующего получателя
- Обновлена форма OrderForm для работы с PhoneNumberField и новым полем notes
- Обновлен шаблон order_form.html для отображения нового поля
- Созданы миграции для изменений модели
- Исправлено: адрес теперь сохраняется для черновиков заказов
- Исправлено: получатель корректно предзаполняется при редактировании заказа
- Исправлено: адрес при редактировании отображается в режиме 'новый' для возможности редактирования
- Исправлено: дата доставки корректно предзаполняется при редактировании заказа
- Исправлено: при редактировании получателя обновляется существующий объект вместо создания нового
- Улучшена логика обработки Delivery для черновиков (создание с опциональными полями)
- Улучшена логика обновления получателя через загрузку заказа из БД с select_related
- Исправлены имена полей времени (time_from/time_to вместо delivery_time_start/end)
- Поля времени сделаны необязательными (дата остается обязательной)
- Добавлен улучшенный UI с быстрыми кнопками для даты и времени
- Поля ввода расположены в один ряд, кнопки быстрого выбора ниже
- Добавлены CSS и JS файлы для улучшенного интерфейса
- Обновлена валидация: время необязательно, но если указано одно - должно быть и другое
- Удалено избыточное поле customer_is_recipient из модели Order
- Добавлено свойство @property is_customer_recipient для обратной совместимости
- Заменены радиокнопки recipient_mode на чекбокс 'Другой получатель' в форме
- Добавлено поле recipient_source для выбора между историей и новым получателем
- Обновлен AddressService.process_recipient_from_form() для работы с чекбоксом
- Обновлены шаблоны: order_form.html (чекбокс вместо радиокнопок) и order_detail.html
- Удалено customer_is_recipient из admin и demo команды
- Создана миграция для удаления поля customer_is_recipient
Логика упрощена: recipient is None = получатель = покупатель, иначе - отдельный получатель
- Добавлены свойства обратной совместимости в модель Order для доступа к полям доставки через связь delivery
- Исправлены фильтры по delivery_date в модели Customer (get_successful_orders_total)
- Исправлены фильтры в orders/filters.py для работы с delivery__delivery_date
- Добавлен select_related('delivery') в customer_detail view для оптимизации запросов
Исправляет ошибку FieldError: Cannot resolve keyword 'delivery_date' into field
- Отделена модель Delivery от Order (OneToOne связь)
- Добавлены обязательные поля delivery_date, time_from, time_to в Delivery
- Delivery обязательна при создании заказа (кроме черновиков)
- Добавлены методы calculate_total() и reset_delivery_cost() в Order
- Добавлена валидация полей доставки в OrderForm
- Исправлено создание доменов - убран порт из домена в БД
- Исправлен редирект после установки пароля (правильный формат URL)
- Исправлена ошибка NoReverseMatch в navbar для public схемы
- Удалены все старые миграции (база создается с нуля)
- Обновлены views для работы с новой моделью Delivery
- Introduced Recipient model to manage order recipients separately from customers.
- Updated Order model to link to Recipient, replacing recipient_name and recipient_phone fields.
- Enhanced OrderForm to include recipient selection modes: customer, history, and new.
- Added AJAX endpoint to fetch recipient history for customers.
- Updated admin interface to manage recipients and display recipient information in order details.
- Refactored address handling to accommodate new recipient logic.
- Improved demo order creation to include random recipients.
Проблема:
- 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 для аудита
Теперь при уменьшении суммы заказа переплата корректно возвращается клиенту
Изменения в 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 автоматически зачислится в кошелёк как переплата
Цель:
- Исключить путаницу в истории транзакций при оплате кошельком
- Разграничить поведение: кошелёк строго ограничен, внешние методы допускают переплату
- Обеспечить прозрачность движения средств для операторов
- Разделен экран на две колонки: заказ слева, оплата справа
- Форма оплаты вынесена за пределы основной формы заказа (устранена проблема вложенных форм)
- Исправлен метод calculate_total() для сохранения итоговой суммы в БД
- Добавлена модель Transaction для учета платежей и возвратов
- Добавлена модель PaymentMethod для методов оплаты
- Удалена старая модель Payment, заменена на Transaction
- Добавлен TransactionService для управления транзакциями
- Обновлен интерфейс форм оплаты для правой колонки
- Кнопка 'Сохранить изменения' теперь работает корректно
Убрано поле скидки из системы для последующей реализации полноценной системы скидок.
Изменения:
- Удалено поле 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
- Modified order_create view to read customer from GET parameter
- Pass preselected_customer to template context
- Template renders select with preselected option for Select2
- Fixed draft creation timing with callback after Select2 initialization
- Auto-create draft when customer is preselected from URL
- Graceful handling if customer not found or invalid ID
- Добавлено поле wallet_balance в модель Customer
- Создана модель WalletTransaction для истории операций
- Реализован сервис WalletService с методами:
* add_overpayment - автоматическое зачисление переплаты
* pay_with_wallet - оплата заказа из кошелька
* adjust_balance - ручная корректировка баланса
- Интеграция с Payment.save() для автоматической обработки переплат
- UI для оплаты из кошелька в деталях заказа
- Отображение баланса и долга на странице клиента
- Админка с inline транзакций и запретом ручного создания
- Добавлен способ оплаты account_balance
- Миграция 0004 для customers приложения