Добавлены поля original_sales_unit и conversion_factor в KitItemSnapshot для хранения
единиц продажи и коэффициентов конверсии на момент создания снимка. Обновлена логика
резервирования запасов для корректного расчета количества в базовых единицах.
Изменения в шаблоне редактирования комплектов для сохранения выбранных единиц продажи
при обновлении списка опций.
BREAKING CHANGE: Изменена структура данных в KitItemSnapshot, требуется миграция базы данных.
- Добавлено поле KitItem.unit_price для хранения зафиксированной цены
- Витринные комплекты больше не обновляются при изменении цен товаров
- Добавлен красный индикатор на карточке если цена неактуальна
- Добавлен warning в модалке редактирования с кнопкой "Актуализировать"
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Добавить inline-редактирование цен в списке товаров
- Оптимизировать карточки товаров в POS-терминале
- Рефакторинг моделей единиц измерения
- Миграция unit -> base_unit в SalesUnit
- Улучшить UI форм создания/редактирования товаров
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Добавлена поддержка выбора основной категории (primary_category) для товаров и наборов, а также новая модель IntegrationCategoryMapping для связи категорий с внешними площадками. Теперь можно указать категорию товара, которая будет использоваться при экспорте на внешние площадки (Recommerce, WooCommerce и др.), с возможностью настройки маппинга категорий для каждого типа интеграции.
- Добавлены поля в BaseProductEntity (наследуются в Product, ProductKit)
- Исправлен формат флагов в Recommerce mappers (1/0 вместо true/false)
- Добавлены чекбоксы в админку Product и ProductKit
- special = is_special OR has_discount (ручное + автоматическое)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Устранена ошибка ValueError "Cannot query 'email': Must be 'PlatformAdmin' instance"
при доступе CustomUser к странице /products/.
Проблема:
- Модели products (TENANT_APPS) использовали get_user_model() для ForeignKey
- get_user_model() возвращал PlatformAdmin (AUTH_USER_MODEL)
- Но tenant модели должны ссылаться на CustomUser (tenant пользователей)
- Это создавало конфликт типов при запросах от CustomUser
Изменения:
1. products/models/base.py:
- Убран get_user_model()
- BaseProductEntity.archived_by теперь ForeignKey('accounts.CustomUser')
2. products/models/categories.py:
- Убран get_user_model()
- ProductCategory.deleted_by теперь ForeignKey('accounts.CustomUser')
3. products/models/import_job.py:
- Убран get_user_model()
- ProductImportJob.user теперь ForeignKey('accounts.CustomUser')
4. Создана миграция 0002 с data migration:
- Очистка некорректных ссылок (установка NULL)
- Изменение типа ForeignKey полей с PlatformAdmin на CustomUser
5. user_roles/auth_backend.py:
- Добавлена функция _is_tenant_user() для проверки типа пользователя
- Исправлена логика has_perm() и has_module_perms()
- CustomUser теперь не проверяется через ModelBackend.has_perm()
6. admin_access_middleware.py:
- Улучшены сообщения об ошибках доступа
- Добавлен рендеринг через шаблон access_denied.html
7. templates/errors/access_denied.html:
- Новый шаблон для красивого отображения ошибок доступа
Результат:
- CustomUser может без ошибок работать со страницей /products/
- Корректная архитектура: tenant модели ссылаются на tenant пользователей
- PlatformAdmin продолжает работать корректно
- Чистое решение без костылей
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- Реализован импорт Product из CSV/XLSX через Celery с прогресс-баром
- Параллельная загрузка фото товаров с внешних URL (масштабируемость до 500+ товаров)
- Добавлена модель ProductImportJob для отслеживания статуса импорта
- Создан таск download_product_photo_async для загрузки фото в фоне
- Интеграция с существующим ImageProcessor (синхронная обработка через use_async=False)
- Добавлены view и template для импорта с real-time обновлением через AJAX
FIX: Исправлен баг со счётчиком SKU - инкремент только после успешного сохранения
- Добавлен SKUCounter.peek_next_value() - возвращает следующий номер БЕЗ инкремента
- Добавлен SKUCounter.increment_counter() - инкрементирует счётчик
- generate_product_sku() использует peek_next_value() вместо get_next_value()
- Добавлен post_save сигнал increment_sku_counter_after_save() для инкремента после создания
- Предотвращает пропуски номеров при ошибках валидации (например cost_price NULL)
FIX: Исправлена ошибка с is_main в ProductPhoto
- ProductPhoto не имеет поля is_main, используется только order
- Первое фото (order=0) автоматически считается главным
- Удалён параметр is_main из download_product_photo_async и _collect_photo_tasks
Изменены файлы:
- products/models/base.py - методы для управления счётчиком SKU
- products/models/import_job.py - модель для отслеживания импорта
- products/services/import_export.py - сервис импорта с поддержкой Celery
- products/tasks.py - таски для асинхронного импорта и загрузки фото
- products/signals.py - сигнал для инкремента счётчика после сохранения
- products/utils/sku_generator.py - использование peek_next_value()
- products/views/product_import_views.py - view для импорта
- products/templates/products/product_import*.html - UI для импорта
- docker/entrypoint.sh - настройка Celery worker (concurrency=4)
- requirements.txt - добавлен requests для загрузки фото
- Добавлен метод calculate_available_quantity() в модель ProductKit для точного расчёта максимального количества комплектов на основе свободных остатков компонентов
- Обновлён метод check_availability() для использования нового расчёта (обратная совместимость)
- Удалён устаревший сервис kit_availability.py
Исправлено отображение остатков комплектов:
- products_list.html: вместо прочерка показывается количество комплектов
- catalog.html: добавлено отображение доступного количества комплектов с цветовой индикацией
- POS terminal.js: в карточке товара показывается конкретное количество вместо общего 'В наличии'
Обновлены представления:
- ProductsListView: аннотирует комплекты атрибутом total_free
- CatalogView: рассчитывает доступное количество для каждого комплекта
- POS get_products(): убран хардкод, используется реальный расчёт по складу
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.
Добавлено поле variant_sku в модель ConfigurableProductOption.
Артикул варианта генерируется автоматически в формате VAR-XXXXXX-V1, VAR-XXXXXX-V2 и т.д.
Счетчик не переиспользуется при удалении вариантов для защиты интеграций.
Переименован property variant_sku в variant_base_sku для основного SKU.
Обновлен шаблон с колонкой артикула варианта.
Создана миграция для добавления поля и data migration для существующих записей.
Назначение: дополнительный артикул для интеграций с внешними площадками.
- Добавлен миксин SKUUniqueMixin для единообразной валидации артикулов
- Валидация проверяет уникальность SKU среди Product, ProductKit, ProductCategory, ConfigurableProduct
- Реализована автогенерация артикулов для ConfigurableProduct (формат VAR-XXXXXX)
- Добавлен новый тип счетчика 'configurable' в SKUCounter
- Обновлены формы Product, ProductKit, ProductCategory, ConfigurableProduct
- Рефакторинг методов clean() в формах: валидация имени вынесена в clean_name()
- Добавлена функция generate_configurable_sku() в sku_generator.py
- Обновлена функция ensure_sku_unique() для проверки ConfigurableProduct
- Добавлен метод save() в модель ConfigurableProduct для автогенерации SKU
- Обновлен шаблон configurableproduct_form.html с отображением help_text для SKU
Код стал чистым, без дублирования логики валидации.
Основные изменения:
- Переименование ConfigurableKitProduct → ConfigurableProduct
- Добавлена поддержка Product как варианта (не только ProductKit)
- Создан справочник атрибутов (ProductAttribute, ProductAttributeValue)
- CRUD для управления атрибутами с inline редактированием значений
- Пересозданы миграции с нуля для всех приложений
- Добавлена ссылка на атрибуты в навигацию
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Добавлено:
- Команда clear_tenant_data для полной очистки данных тенанта без удаления схемы
* Очищает все таблицы через TRUNCATE CASCADE
* Сбрасывает ID-последовательности
* Сохраняет схему БД и запись Client
* Поддержка флага --noinput для автоматизации
- Команда init_tenant_data для инициализации системных данных тенанта
* Создаёт системного клиента (АНОНИМНЫЙ ПОКУПАТЕЛЬ для POS)
* Создаёт 8 системных статусов заказов
* Создаёт 5 системных способов оплаты
* Поддержка флага --reset для пересоздания данных
Исправлено:
- Заменены устаревшие фильтры is_active на status='active' для Product и ProductKit
* products/views/category_views.py: исправлены фильтры в build_category_tree и get_context_data
* products/services/kit_pricing.py: исправлены фильтры при получении товаров из variant_group
* products/models/kits.py: исправлен фильтр в get_available_products
* Устранена ошибка FieldError при работе со списком категорий
Улучшено:
- Команда clear_tenant_data теперь предлагает пользователю инициализировать системные данные после очистки
- Добавлена детальная информация о процессе очистки и инициализации данных
- Удалено ручное редактирование себестоимости из формы товара
- Себестоимость теперь рассчитывается автоматически из партий (FIFO)
- Добавлена модель CostPriceHistory для логирования изменений
- Добавлен signal для автоматического логирования изменений cost_price
- Админ-панель: себестоимость read-only с детальной информацией о партиях
- Фронтенд: цены перемещены под название, теги под категории
- Поле cost_price сделано опциональным (default=0) для создания товаров
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Resolves critical bug where photos of products with the same ID in different
tenants were overwriting each other. Implemented complete isolation of media
files between tenants using custom Django storage backend.
## Changes
### New Files
- products/utils/storage.py: TenantAwareFileSystemStorage backend
* Automatically adds tenant_id to file paths on disk
* Prevents cross-tenant file access with security checks
* Stores clean paths in DB for portability
- products/tests/test_multi_tenant_photos.py: Comprehensive tests
* 5 tests covering isolation, security, and configuration
* All tests passing ✅
- MULTITENANT_PHOTO_FIX.md: Complete documentation
### Modified Files
- settings.py: Configured DEFAULT_FILE_STORAGE to use TenantAwareFileSystemStorage
- products/models/photos.py:
* Converted upload_to from strings to callable functions
* Updated ProductPhoto, ProductKitPhoto, ProductCategoryPhoto
* Added tenant isolation documentation
- products/tasks.py: Added documentation about file structure
- products/utils/image_processor.py: Added documentation
- products/utils/image_service.py: Added documentation
## Architecture
**On disk:** media/tenants/{tenant_id}/products/{entity_id}/{photo_id}/{size}.ext
**In DB:** products/{entity_id}/{photo_id}/{size}.ext
Tenant ID is automatically added/removed during file operations.
## Security
- Storage rejects cross-tenant file access
- Proper tenant context validation
- Integration with django-tenants schema system
## Testing
- All 5 multi-tenant photo tests pass
- Verified photo paths are isolated per tenant
- Verified storage rejects cross-tenant access
- Verified configuration is correct
## Future-proof
- Ready for S3 migration (just change storage backend)
- No breaking changes to existing code
- Clean separation of concerns
Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Fixed:
- Replace is_active with status='active' for Product filtering in IncomingModelForm
- Product model uses status field instead of is_active
Added:
- Showcase field to ProductKit for tracking showcase placement
- product_kit field to Reservation for tracking kit-specific reservations
- Disassemble button in POS terminal for showcase kits
- API endpoint for kit disassembly (release reservations, mark discontinued)
- Improved reservation filtering when dismantling specific kits
Changes:
- ShowcaseManager now links reservations to specific kit instances
- POS terminal modal shows disassemble button in edit mode
- Kit disassembly properly updates stock aggregates
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implementation of kit binding feature for ConfigurableKitProduct variants:
- Added ForeignKey field `kit` to ConfigurableKitProductAttribute
* References ProductKit with CASCADE delete
* Optional field (blank=True, null=True)
* Indexed for efficient queries
- Created migration 0007_add_kit_to_attribute
* Handles existing data (NULL values for all current records)
* Properly indexed for performance
- Updated template configurablekit_form.html
* Injected available ProductKits into JavaScript
* Added kit selector dropdown in card interface
* Each value now has associated kit selection
* JavaScript validates kit selection alongside values
- Updated JavaScript in card interface
* serializeAttributeValues() now collects kit IDs
* Creates parallel JSON arrays: values and kits
* Stores in hidden fields: attributes-X-values and attributes-X-kits
- Updated views _save_attributes_from_cards() in both Create and Update
* Reads kit IDs from POST JSON
* Looks up ProductKit objects
* Creates ConfigurableKitProductAttribute with FK populated
* Gracefully handles missing kits
- Fixed _should_delete_form() method
* More robust handling of formset deletion_field
* Works with all formset types
- Updated __str__() method
* Handles NULL kit case
Example workflow:
Dlina: 50 -> Kit A, 60 -> Kit B, 70 -> Kit C
Upakovka: BEZ -> Kit A, V_UPAKOVKE -> (no kit)
Tested with test_kit_binding.py - all tests passing
- Kit creation and retrieval
- Attribute creation with kit FK
- Mixed kit-bound and unbound attributes
- Querying attributes by kit
- Reverse queries (get kit for attribute value)
Added documentation: KIT_BINDING_IMPLEMENTATION.md
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
This commit introduces a complete refactoring of the variable product system:
1. **New Model**: ConfigurableKitOptionAttribute - M2M relationship between variants and attribute values
- Replaces TextField-based attribute storage with proper database relationships
- Ensures one value per attribute per variant through unique_together constraint
- Includes indexes on both option and attribute fields for query performance
2. **Form Refactoring**:
- Removed static 'attributes' field from ConfigurableKitOptionForm
- Added dynamic field generation in __init__ based on parent attributes
- Creates ModelChoiceField for each attribute (e.g., attribute_Длина, attribute_Упаковка)
- Enhanced BaseConfigurableKitOptionFormSet validation to check all attributes are filled
3. **View Updates**:
- Modified ConfigurableKitProductCreateView.form_valid() to save M2M relationships
- Modified ConfigurableKitProductUpdateView.form_valid() with same logic
- Uses transaction.atomic() for data consistency
4. **Template & JS Enhancements**:
- Reordered form so attributes section appears before variants
- Fixed template syntax: changed from field.name.startswith to "attribute_" in field.name
- Updated JavaScript to dynamically generate attribute select fields when adding variants
- Properly handles formset naming convention (options-{idx}-attribute_{name})
5. **Database Migrations**:
- Created migration 0005 to alter ConfigurableKitOption.attributes to JSONField (for future use)
- Created migration 0006 to add ConfigurableKitOptionAttribute model
6. **Tests**:
- Added test_configurable_simple.py for model/form verification
- Added test_workflow.py for complete end-to-end testing
- All tests passing successfully
Features:
✓ All attributes required for each variant if defined on parent
✓ One value per attribute per variant (unique_together constraint)
✓ One default variant per product (formset validation)
✓ Dynamic form field generation based on parent attributes
✓ Atomic transactions for multi-part operations
✓ Proper error messages per variant number
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
- Создана модель ConfigurableKitProductAttribute с полями name, option, position, visible
- Добавлены формы и formsets для управления атрибутами родительского товара
- Обновлены CRUD представления для работы с атрибутами (создание/редактирование)
- Добавлен блок атрибутов в шаблоны создания/редактирования
- Обновлена страница детального просмотра с отображением атрибутов товара
- Добавлен JavaScript для динамического добавления форм атрибутов
- Реализована валидация дубликатов атрибутов в formset
- Атрибуты сохраняются в transaction.atomic() вместе с вариантами
Теперь можно определять схему атрибутов для экспорта на WooCommerce без использования JSON или ID, только name и option.
- Добавлен retry на 5 сек при DoesNotExist для ожидания коммита транзакции
- temp_path сохраняется в PhotoProcessingStatus.result_data при постановке задачи
- При окончательной неудаче not_found удаляется осиротевший temp файл
- Предотвращает накопление temp файлов при гонке создания фото
- Added temp file deletion in Celery task after successful processing
- Added temp file cleanup in sync fallback method
- Added temp file removal in delete() if processing never completed
- Prevents accumulation of orphaned files in media/<entity>/temp/ folders
Реализована трёхуровневая система статусов товаров и комплектов:
- active (Активный) - товар доступен для продажи
- archived (Архивный) - скрыт, можно восстановить в следующем сезоне
- discontinued (Снят) - морально устарел, готов к удалению
Изменения:
1. Модели (BaseProductEntity, Product, ProductKit):
- Заменено поле is_deleted (Boolean) на status (CharField)
- Добавлены архивные метаданные (archived_at, archived_by)
- Обновлены методы: archive(), restore(), discontinue(), delete()
- Уникальное ограничение изменено на conditional (status='active')
2. Менеджеры (ActiveManager, SoftDeleteQuerySet):
- Полиморфная поддержка обеих систем (status и is_active)
- Использует hasattr() для совместимости с наследниками
- Методы: archive(), restore(), discontinue(), archived_only(), active_only()
3. Формы (ProductForm, ProductKitForm):
- Включены поле status в формы
- Валидация уникальности по status='active'
- CSS классы для статус-селектора
4. Admin панель:
- DeletedFilter переименован в StatusFilter с тремя опциями
- get_status_display() с цветным отображением статуса
- Actions: restore_items, hard_delete_selected, delete_selected
- Readonly поля для архивирования
5. Представления:
- ProductListView: фильтр status вместо is_active
- CombinedProductListView: поддержка фильтра status для товаров и комплектов
- API views обновлены для работы со статусом
6. Шаблоны:
- product_form.html: form.status вместо form.is_active
- productkit_create.html: form.status вместо form.is_active
- productkit_edit.html: form.status вместо form.is_active
7. Миграции:
- Удалены все старые миграции (чистый перезапуск по требованию пользователя)
- Создана новая миграция 0001_initial с полной структурой status-системы
- Удален старый код преобразования is_deleted -> status
Проведённые проверки:
- Django system check passed ✓
- Полиморфные менеджеры работают с обеими системами
- Уникальные ограничения корректно работают с условиями
- История заказов сохраняется даже после архивирования товара (django-simple-history)
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Реализована полная система обеспечения уникальности названий:
1. **Уровень БД (Model Constraints)** - добавлены UniqueConstraint для:
- Product: уникальность имени среди активных товаров
- ProductCategory: уникальность имени среди активных категорий
- ProductTag: уникальность имени только для активных тегов (неактивные могут повторяться)
- ProductKit: уникальность имени среди активных, непроизвременных комплектов
2. **Уровень формы (Form Validation)** - добавлены clean() методы для:
- ProductForm, ProductKitForm, ProductCategoryForm, ProductTagForm
- Валидация до попытки сохранения в БД
- Сохранение введённых данных при ошибке валидации
3. **Уровень представления (IntegrityError Handling)** - добавлена обработка в views:
- ProductCategoryCreateView, ProductCategoryUpdateView
- ProductTagCreateView, ProductTagUpdateView
- ProductKitCreateView, ProductKitUpdateView
- create_tag_api: защита от race conditions с fallback поиском
Три уровня защиты гарантируют:
- Профилактика ошибок на уровне формы
- Обработка исключительных ситуаций в views
- Защита БД от одновременных запросов (race conditions)
- Пользователь видит понятное сообщение об ошибке вместо 500 ошибки
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Реализована трёхуровневая защита от IntegrityError при создании товаров с одинаковым названием:
1. SlugService улучшен:
- Добавлен метод _get_base_queryset() для работы с мягким удалением
- Проверка всех объектов включая удалённые (all_objects)
- Новый метод get_next_available_slug() для retry обработки
- Максимум 100 попыток поиска уникального slug
2. BaseProductEntity.save() защищена:
- transaction.atomic() для атомарности операции
- Retry логика с 5 попытками при IntegrityError
- При конфликте добавляется суффикс (-1, -2, -3...)
- Fallback на timestamp если суффиксы исчерпаны
3. Views обрабатывают IntegrityError:
- ProductCreateView.form_valid() перехватывает ошибку
- ProductUpdateView.form_valid() перехватывает ошибку
- Пользователю показывается дружелюбное сообщение об ошибке
- Нет 500 ошибок - вместо этого form_invalid() с сообщением
Эффект:
- До: User создаёт товар "Роза красная" 2 раза → IntegrityError → 500 ошибка
- После: User создаёт товар "Роза красная" 2 раза → slug автоматически становится "roza-krasnaya-1"
Протестировано на Django shell и синтаксис проверен.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
При асинхронной обработке фото нужно сначала сохранить файл в БД,
потом запустить Celery task. Иначе task не найдет файл.
Изменения:
- BasePhoto.save() теперь сохраняет файл перед запуском task
- Исправлена проблема 'Photo has no image file' в Celery worker
🤖 Generated with Claude Code
## Изменения:
### 1. ProductKit - расчет цены для вариантов товаров
- Добавлена обработка variant_group в методах расчета base_price
- Теперь учитываются варианты товаров при расчете стоимости комплекта
### 2. DraftOrderService - упрощение логики автосохранения
- Удалена проверка is_draft() при обновлении (позволяет обновлять заказы в других статусах)
- Улучшена документация метода update_draft
### 3. Шаблоны и скрипты
- Обновлены шаблоны форм создания/редактирования комплектов
- Обновлены скрипты автосохранения
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Упрощена модель ProductTag:
- Удалены поля soft delete (is_deleted, deleted_at, deleted_by)
- Добавлено поле is_active для управления статусом
- Упрощены менеджеры и методы модели
Создан CRUD функционал:
- ProductTagForm: форма с автогенерацией slug
- Views: список, создание, просмотр, редактирование, удаление
- URL маршруты: /products/tags/*
- Шаблоны: list, form, detail, confirm_delete
Особенности:
- Поиск по названию и slug
- Фильтрация по статусу активности
- Статистика использования тегов в товарах/комплектах
- Пагинация (20 на страницу)
- Предупреждение при удалении с отображением связанных объектов
- Добавлена ссылка "Теги" в навигацию
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
- Добавлено поле is_temporary для пометки временных комплектов
- Добавлено поле order для связи с заказом
- Добавлены индексы для производительности
- Добавлен метод make_permanent() для преобразования в постоянный комплект
Временные комплекты создаются для конкретных заказов и не показываются
в общем каталоге, но хранятся в БД для истории и анализа.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Улучшения:
- Исправлена отображение цены в таблице вариантов: заменено sale_price на actual_price
чтобы правильно обрабатывать случаи когда скидка не установлена
- Оптимизирован property in_stock: вычисляется в памяти из prefetched данных
вместо отдельного запроса EXISTS к БД
- Оптимизирован property price: использует actual_price (sale_price или price)
вместо только sale_price, добавлена документация о требовании prefetch_related
- Оптимизирован DetailView.get_context_data: используется кешированный
prefetch_related вместо создания нового queryset для items
- Исправлена AJAX функция _get_items_data: использует actual_price вместо sale_price
Результат:
- Исчезла проблема с выводом "None" вместо цены
- Сокращено количество запросов к БД с 4-5 до 3 для страницы detail
- Улучшена производительность при работе с группами вариантов
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Удалено ненужное поле 'notes' из формы создания/редактирования комплектов:
- Удалено из модели KitItem
- Удалено из формы KitItemForm
- Удалено из template kititem_formset.html
- Удалено из formset'ов KitItemFormSetCreate и KitItemFormSetUpdate
- Создана миграция БД для удаления поля из базы данных
Теперь каждый товар в комплекте отображается с 4 полями:
- Товар (или Группа вариантов)
- Количество
- Кнопка удаления
- ID (скрытое)
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Исправлены 4 проблемы:
1. Расчёт цены первого товара - улучшена валидация в getProductPrice и calculateFinalPrice
2. Отображение actual_price в Select2 вместо обычной цены
3. Количество по умолчанию = 1 для новых форм компонентов
4. Auto-select текста при клике на поле количества для удобства редактирования
Изменённые файлы:
- products/forms.py: добавлен __init__ в KitItemForm для quantity.initial = 1
- products/templates/includes/select2-product-init.html: обновлена formatSelectResult
- products/templates/productkit_create.html: добавлен focus handler для auto-select
- products/templates/productkit_edit.html: добавлен focus handler для auto-select
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
Добавлена полностью гибкая система для оценки качества фотографий на основе размеров:
Конфигурация (settings.py):
- IMAGE_QUALITY_LEVELS: Пороги качества как доля от максимума (95%, 70%, 40%, 20%)
- IMAGE_QUALITY_LABELS: Описания, цвета и рекомендации для каждого уровня
- Система полностью адаптивна - меняется max_width в settings → пороги пересчитываются
Валидатор (validators/image_validators.py):
- get_max_dimension_from_config() - динамически читает из IMAGE_PROCESSING_CONFIG
- get_image_quality_level() - определяет уровень качества (excellent/good/acceptable/poor/very_poor)
- get_quality_info() - информация о уровне из IMAGE_QUALITY_LABELS
- validate_product_image() - комплексная валидация для UI
Модели (models/photos.py):
- ProductPhoto, ProductKitPhoto, ProductCategoryPhoto дополнены полями:
- quality_level: уровень качества (CharField с choices)
- quality_warning: требует ли обновления (BooleanField)
- Добавлены индексы для быстрого поиска товаров требующих обновления фото
Обработчик (image_processor.py):
- process_image() теперь возвращает дополнительно:
- width, height: размеры оригинального изображения
- quality_level: уровень качества
- quality_warning: нужно ли обновить перед выгрузкой
- Вызывает валидатор автоматически при обработке
Логика сохранения (photos.py -> save()):
- При сохранении нового фото автоматически вычисляет quality_level и quality_warning
- При обновлении существующего фото пересчитывает качество
- Сохраняет все три поля атомарно
Система полностью готова к:
- Phase 2: Admin интерфейс с фильтрами и индикаторами
- Phase 3: Фронтенд с визуальными индикаторами в формах и API
Примеры использования:
>>> from products.validators.image_validators import get_image_quality_level
>>> get_image_quality_level(1400, 1400) # если max=2160
('good', False) # 1400/2160 = 64.8% >= 70%? Нет, но >= 40%
>>> get_image_quality_level(400, 400)
('poor', True) # Требует обновления
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>