Рефакторинг: отделение Delivery от Order, обязательные поля доставки, исправление доменов
- Отделена модель Delivery от Order (OneToOne связь) - Добавлены обязательные поля delivery_date, time_from, time_to в Delivery - Delivery обязательна при создании заказа (кроме черновиков) - Добавлены методы calculate_total() и reset_delivery_cost() в Order - Добавлена валидация полей доставки в OrderForm - Исправлено создание доменов - убран порт из домена в БД - Исправлен редирект после установки пароля (правильный формат URL) - Исправлена ошибка NoReverseMatch в navbar для public схемы - Удалены все старые миграции (база создается с нуля) - Обновлены views для работы с новой моделью Delivery
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-15 11:57
|
||||
# Generated by Django 5.0.10 on 2025-12-23 20:38
|
||||
|
||||
import django.db.models.deletion
|
||||
import simple_history.models
|
||||
from decimal import Decimal
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
@@ -17,28 +18,54 @@ class Migration(migrations.Migration):
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='KitItemSnapshot',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('product_name', models.CharField(blank=True, max_length=200, verbose_name='Название товара')),
|
||||
('product_sku', models.CharField(blank=True, max_length=100, verbose_name='Артикул товара')),
|
||||
('product_price', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=10, verbose_name='Цена товара')),
|
||||
('variant_group_name', models.CharField(blank=True, max_length=200, verbose_name='Группа вариантов')),
|
||||
('quantity', models.DecimalField(decimal_places=3, max_digits=10, verbose_name='Количество')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Снимок компонента',
|
||||
'verbose_name_plural': 'Снимки компонентов',
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='KitSnapshot',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Название')),
|
||||
('sku', models.CharField(blank=True, max_length=100, verbose_name='Артикул')),
|
||||
('description', models.TextField(blank=True, verbose_name='Описание')),
|
||||
('base_price', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=10, verbose_name='Базовая цена')),
|
||||
('price', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=10, verbose_name='Итоговая цена')),
|
||||
('sale_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Цена со скидкой')),
|
||||
('price_adjustment_type', models.CharField(default='none', max_length=20, verbose_name='Тип корректировки')),
|
||||
('price_adjustment_value', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=10, verbose_name='Значение корректировки')),
|
||||
('is_temporary', models.BooleanField(default=False, verbose_name='Временный комплект')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Снимок комплекта',
|
||||
'verbose_name_plural': 'Снимки комплектов',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Order',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('order_number', models.PositiveIntegerField(editable=False, help_text='Уникальный номер заказа', unique=True, verbose_name='Номер заказа')),
|
||||
('is_delivery', models.BooleanField(default=True, help_text='True - доставка курьером, False - самовывоз', verbose_name='С доставкой')),
|
||||
('delivery_date', models.DateField(blank=True, help_text='Может быть заполнено позже', null=True, verbose_name='Дата доставки/самовывоза')),
|
||||
('delivery_time_start', models.TimeField(blank=True, help_text='Начало временного интервала', null=True, verbose_name='Время от')),
|
||||
('delivery_time_end', models.TimeField(blank=True, help_text='Конец временного интервала', null=True, verbose_name='Время до')),
|
||||
('delivery_cost', models.DecimalField(decimal_places=2, default=0, help_text='0 для самовывоза', max_digits=10, verbose_name='Стоимость доставки')),
|
||||
('is_custom_delivery_cost', models.BooleanField(default=False, help_text='True если стоимость доставки была изменена вручную', verbose_name='Стоимость доставки установлена вручную')),
|
||||
('is_returned', models.BooleanField(default=False, help_text='True если заказ был выполнен, но потом отменен или возвращен клиентом', verbose_name='Возвращен')),
|
||||
('last_autosave_at', models.DateTimeField(blank=True, help_text='Время последнего автоматического сохранения черновика', null=True, verbose_name='Последнее автосохранение')),
|
||||
('payment_method', models.CharField(choices=[('cash_to_courier', 'Наличные курьеру'), ('card_to_courier', 'Карта курьеру'), ('online', 'Онлайн оплата'), ('bank_transfer', 'Банковский перевод')], default='cash_to_courier', max_length=20, verbose_name='Способ оплаты')),
|
||||
('is_paid', models.BooleanField(default=False, verbose_name='Оплачен')),
|
||||
('total_amount', models.DecimalField(decimal_places=2, default=0, help_text='Общая сумма заказа включая доставку', max_digits=10, verbose_name='Итоговая сумма заказа')),
|
||||
('discount_amount', models.DecimalField(decimal_places=2, default=0, help_text='Применяется вручную или через систему скидок', max_digits=10, verbose_name='Сумма скидки')),
|
||||
('total_amount', models.DecimalField(decimal_places=2, default=0, help_text='Общая сумма заказа', max_digits=10, verbose_name='Итоговая сумма заказа')),
|
||||
('amount_paid', models.DecimalField(decimal_places=2, default=0, help_text='Сумма, внесенная клиентом', max_digits=10, verbose_name='Оплачено')),
|
||||
('payment_status', models.CharField(choices=[('unpaid', 'Не оплачен'), ('partial', 'Частично оплачен'), ('paid', 'Оплачен полностью')], default='unpaid', help_text='Обновляется автоматически при добавлении платежей', max_length=20, verbose_name='Статус оплаты')),
|
||||
('customer_is_recipient', models.BooleanField(default=True, help_text='Если отмечено, данные получателя не требуются отдельно', verbose_name='Покупатель является получателем')),
|
||||
('recipient_name', models.CharField(blank=True, help_text='Заполняется, если покупатель не является получателем', max_length=200, null=True, verbose_name='Имя получателя')),
|
||||
('recipient_phone', models.CharField(blank=True, help_text='Контактный телефон получателя', max_length=20, null=True, verbose_name='Телефон получателя')),
|
||||
('is_anonymous', models.BooleanField(default=False, help_text='Не сообщать получателю имя отправителя', verbose_name='Анонимная доставка')),
|
||||
('special_instructions', models.TextField(blank=True, help_text='Комментарии и пожелания к заказу', null=True, verbose_name='Особые пожелания')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
@@ -54,9 +81,12 @@ class Migration(migrations.Migration):
|
||||
name='OrderItem',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('item_name_snapshot', models.CharField(default='', max_length=200, verbose_name='Название на момент заказа')),
|
||||
('item_sku_snapshot', models.CharField(blank=True, max_length=100, verbose_name='Артикул на момент заказа')),
|
||||
('quantity', models.PositiveIntegerField(default=1, verbose_name='Количество')),
|
||||
('price', models.DecimalField(decimal_places=2, help_text='Цена на момент создания заказа (фиксируется)', max_digits=10, verbose_name='Цена за единицу')),
|
||||
('is_custom_price', models.BooleanField(default=False, help_text='True если цена была изменена вручную при создании заказа', verbose_name='Цена изменена вручную')),
|
||||
('is_from_showcase', models.BooleanField(default=False, help_text='True если товар продан с витрины', verbose_name='С витрины')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата добавления')),
|
||||
],
|
||||
options={
|
||||
@@ -87,26 +117,59 @@ class Migration(migrations.Migration):
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Payment',
|
||||
name='PaymentMethod',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('amount', models.DecimalField(decimal_places=2, max_digits=10, verbose_name='Сумма платежа')),
|
||||
('payment_method', models.CharField(choices=[('cash_to_courier', 'Наличные курьеру'), ('card_to_courier', 'Карта курьеру'), ('online', 'Онлайн оплата'), ('bank_transfer', 'Банковский перевод')], max_length=20, verbose_name='Способ оплаты')),
|
||||
('payment_date', models.DateTimeField(auto_now_add=True, verbose_name='Дата и время платежа')),
|
||||
('notes', models.TextField(blank=True, help_text='Дополнительная информация о платеже', null=True, verbose_name='Примечания')),
|
||||
('code', models.SlugField(help_text="Уникальный идентификатор (например: 'cash_to_courier', 'card_to_courier')", unique=True, verbose_name='Код способа оплаты')),
|
||||
('name', models.CharField(max_length=100, verbose_name='Название способа оплаты')),
|
||||
('description', models.TextField(blank=True, help_text='Дополнительная информация о способе оплаты', verbose_name='Описание')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Отключенные способы оплаты не отображаются при создании заказа', verbose_name='Активен')),
|
||||
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок отображения')),
|
||||
('is_system', models.BooleanField(default=False, help_text='Системные способы оплаты нельзя удалить через интерфейс', verbose_name='Системный')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Платеж',
|
||||
'verbose_name_plural': 'Платежи',
|
||||
'ordering': ['-payment_date'],
|
||||
'verbose_name': 'Способ оплаты',
|
||||
'verbose_name_plural': 'Способы оплаты',
|
||||
'ordering': ['order', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Recipient',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='ФИО или название организации получателя', max_length=200, verbose_name='Имя получателя')),
|
||||
('phone', models.CharField(help_text='Контактный телефон для связи с получателем', max_length=20, verbose_name='Телефон получателя')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Получатель',
|
||||
'verbose_name_plural': 'Получатели',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Transaction',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('transaction_type', models.CharField(choices=[('payment', 'Платёж'), ('refund', 'Возврат')], default='payment', max_length=20, verbose_name='Тип транзакции')),
|
||||
('amount', models.DecimalField(decimal_places=2, help_text="Всегда положительная. Для возврата используется transaction_type='refund'", max_digits=10, verbose_name='Сумма')),
|
||||
('transaction_date', models.DateTimeField(auto_now_add=True, verbose_name='Дата и время транзакции')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('reason', models.CharField(blank=True, help_text='Причина возврата или особенности платежа', max_length=255, null=True, verbose_name='Причина')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Транзакция',
|
||||
'verbose_name_plural': 'Транзакции',
|
||||
'ordering': ['-transaction_date'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Address',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('recipient_name', models.CharField(blank=True, help_text='Имя человека, которому будет доставлен заказ', max_length=200, null=True, verbose_name='Имя получателя')),
|
||||
('recipient_phone', models.CharField(blank=True, help_text='Контактный телефон получателя для уточнения адреса', max_length=20, null=True, verbose_name='Телефон получателя')),
|
||||
('street', models.CharField(blank=True, max_length=255, null=True, verbose_name='Улица')),
|
||||
('building_number', models.CharField(blank=True, max_length=20, null=True, verbose_name='Номер здания')),
|
||||
('apartment_number', models.CharField(blank=True, max_length=20, null=True, verbose_name='Номер квартиры/офиса')),
|
||||
@@ -125,28 +188,38 @@ class Migration(migrations.Migration):
|
||||
'indexes': [models.Index(fields=['created_at'], name='orders_addr_created_98ad97_idx')],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Delivery',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('delivery_type', models.CharField(choices=[('courier', 'Доставка курьером'), ('pickup', 'Самовывоз')], db_index=True, default='courier', max_length=20, verbose_name='Способ доставки')),
|
||||
('delivery_date', models.DateField(help_text='Дата, когда должна быть выполнена доставка', verbose_name='Дата доставки')),
|
||||
('time_from', models.TimeField(help_text='Начальное время временного интервала доставки', verbose_name='Время доставки от')),
|
||||
('time_to', models.TimeField(help_text='Конечное время временного интервала доставки', verbose_name='Время доставки до')),
|
||||
('cost', models.DecimalField(decimal_places=2, default=0, help_text='Стоимость доставки в рублях. 0 для бесплатной доставки/самовывоза', max_digits=10, verbose_name='Стоимость доставки')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||
('address', models.ForeignKey(blank=True, help_text='Адрес для курьерской доставки. На один адрес может быть много доставок', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='deliveries', to='orders.address', verbose_name='Адрес доставки')),
|
||||
('pickup_warehouse', models.ForeignKey(blank=True, help_text='Склад для самовывоза заказа', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='deliveries', to='inventory.warehouse', verbose_name='Склад самовывоза')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Доставка',
|
||||
'verbose_name_plural': 'Доставки',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='HistoricalOrder',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('order_number', models.PositiveIntegerField(db_index=True, editable=False, help_text='Уникальный номер заказа', verbose_name='Номер заказа')),
|
||||
('is_delivery', models.BooleanField(default=True, help_text='True - доставка курьером, False - самовывоз', verbose_name='С доставкой')),
|
||||
('delivery_date', models.DateField(blank=True, help_text='Может быть заполнено позже', null=True, verbose_name='Дата доставки/самовывоза')),
|
||||
('delivery_time_start', models.TimeField(blank=True, help_text='Начало временного интервала', null=True, verbose_name='Время от')),
|
||||
('delivery_time_end', models.TimeField(blank=True, help_text='Конец временного интервала', null=True, verbose_name='Время до')),
|
||||
('delivery_cost', models.DecimalField(decimal_places=2, default=0, help_text='0 для самовывоза', max_digits=10, verbose_name='Стоимость доставки')),
|
||||
('is_custom_delivery_cost', models.BooleanField(default=False, help_text='True если стоимость доставки была изменена вручную', verbose_name='Стоимость доставки установлена вручную')),
|
||||
('is_returned', models.BooleanField(default=False, help_text='True если заказ был выполнен, но потом отменен или возвращен клиентом', verbose_name='Возвращен')),
|
||||
('last_autosave_at', models.DateTimeField(blank=True, help_text='Время последнего автоматического сохранения черновика', null=True, verbose_name='Последнее автосохранение')),
|
||||
('payment_method', models.CharField(choices=[('cash_to_courier', 'Наличные курьеру'), ('card_to_courier', 'Карта курьеру'), ('online', 'Онлайн оплата'), ('bank_transfer', 'Банковский перевод')], default='cash_to_courier', max_length=20, verbose_name='Способ оплаты')),
|
||||
('is_paid', models.BooleanField(default=False, verbose_name='Оплачен')),
|
||||
('total_amount', models.DecimalField(decimal_places=2, default=0, help_text='Общая сумма заказа включая доставку', max_digits=10, verbose_name='Итоговая сумма заказа')),
|
||||
('discount_amount', models.DecimalField(decimal_places=2, default=0, help_text='Применяется вручную или через систему скидок', max_digits=10, verbose_name='Сумма скидки')),
|
||||
('total_amount', models.DecimalField(decimal_places=2, default=0, help_text='Общая сумма заказа', max_digits=10, verbose_name='Итоговая сумма заказа')),
|
||||
('amount_paid', models.DecimalField(decimal_places=2, default=0, help_text='Сумма, внесенная клиентом', max_digits=10, verbose_name='Оплачено')),
|
||||
('payment_status', models.CharField(choices=[('unpaid', 'Не оплачен'), ('partial', 'Частично оплачен'), ('paid', 'Оплачен полностью')], default='unpaid', help_text='Обновляется автоматически при добавлении платежей', max_length=20, verbose_name='Статус оплаты')),
|
||||
('customer_is_recipient', models.BooleanField(default=True, help_text='Если отмечено, данные получателя не требуются отдельно', verbose_name='Покупатель является получателем')),
|
||||
('recipient_name', models.CharField(blank=True, help_text='Заполняется, если покупатель не является получателем', max_length=200, null=True, verbose_name='Имя получателя')),
|
||||
('recipient_phone', models.CharField(blank=True, help_text='Контактный телефон получателя', max_length=20, null=True, verbose_name='Телефон получателя')),
|
||||
('is_anonymous', models.BooleanField(default=False, help_text='Не сообщать получателю имя отправителя', verbose_name='Анонимная доставка')),
|
||||
('special_instructions', models.TextField(blank=True, help_text='Комментарии и пожелания к заказу', null=True, verbose_name='Особые пожелания')),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Дата создания')),
|
||||
@@ -156,10 +229,8 @@ class Migration(migrations.Migration):
|
||||
('history_change_reason', models.CharField(max_length=100, null=True)),
|
||||
('history_type', models.CharField(choices=[('+', 'Created'), ('~', 'Changed'), ('-', 'Deleted')], max_length=1)),
|
||||
('customer', models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='customers.customer', verbose_name='Клиент')),
|
||||
('delivery_address', models.ForeignKey(blank=True, db_constraint=False, help_text='Обязательно для курьерской доставки', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='orders.address', verbose_name='Адрес доставки')),
|
||||
('history_user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to=settings.AUTH_USER_MODEL)),
|
||||
('modified_by', models.ForeignKey(blank=True, db_constraint=False, help_text='Последний пользователь, изменивший заказ', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to=settings.AUTH_USER_MODEL, verbose_name='Изменен пользователем')),
|
||||
('pickup_warehouse', models.ForeignKey(blank=True, db_constraint=False, help_text='Обязательно для самовывоза', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='inventory.warehouse', verbose_name='Склад для самовывоза')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'historical Заказ',
|
||||
@@ -173,9 +244,12 @@ class Migration(migrations.Migration):
|
||||
name='HistoricalOrderItem',
|
||||
fields=[
|
||||
('id', models.BigIntegerField(auto_created=True, blank=True, db_index=True, verbose_name='ID')),
|
||||
('item_name_snapshot', models.CharField(default='', max_length=200, verbose_name='Название на момент заказа')),
|
||||
('item_sku_snapshot', models.CharField(blank=True, max_length=100, verbose_name='Артикул на момент заказа')),
|
||||
('quantity', models.PositiveIntegerField(default=1, verbose_name='Количество')),
|
||||
('price', models.DecimalField(decimal_places=2, help_text='Цена на момент создания заказа (фиксируется)', max_digits=10, verbose_name='Цена за единицу')),
|
||||
('is_custom_price', models.BooleanField(default=False, help_text='True если цена была изменена вручную при создании заказа', verbose_name='Цена изменена вручную')),
|
||||
('is_from_showcase', models.BooleanField(default=False, help_text='True если товар продан с витрины', verbose_name='С витрины')),
|
||||
('created_at', models.DateTimeField(blank=True, editable=False, verbose_name='Дата добавления')),
|
||||
('history_id', models.AutoField(primary_key=True, serialize=False)),
|
||||
('history_date', models.DateTimeField(db_index=True)),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-15 11:57
|
||||
# Generated by Django 5.0.10 on 2025-12-23 20:38
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
@@ -10,7 +10,7 @@ class Migration(migrations.Migration):
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('customers', '0001_initial'),
|
||||
('customers', '0002_initial'),
|
||||
('inventory', '0002_initial'),
|
||||
('orders', '0001_initial'),
|
||||
('products', '0001_initial'),
|
||||
@@ -29,30 +29,55 @@ class Migration(migrations.Migration):
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='products.productkit', verbose_name='Комплект товаров'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='order',
|
||||
name='customer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='orders', to='customers.customer', verbose_name='Клиент'),
|
||||
model_name='historicalorderitem',
|
||||
name='showcase',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, help_text='Витрина, с которой был продан товар', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='inventory.showcase', verbose_name='Витрина'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kititemsnapshot',
|
||||
name='original_product',
|
||||
field=models.ForeignKey(blank=True, help_text='Ссылка на товар для резервирования на складе', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='kit_item_snapshots', to='products.product', verbose_name='Оригинальный товар'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kitsnapshot',
|
||||
name='original_kit',
|
||||
field=models.ForeignKey(blank=True, help_text='Ссылка на комплект, с которого создан снимок', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='snapshots', to='products.productkit', verbose_name='Оригинальный комплект'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='kititemsnapshot',
|
||||
name='kit_snapshot',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='orders.kitsnapshot', verbose_name='Снимок комплекта'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalorderitem',
|
||||
name='kit_snapshot',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, help_text='Хранит состав комплекта на момент оформления заказа', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='orders.kitsnapshot', verbose_name='Снимок комплекта'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='order',
|
||||
name='delivery_address',
|
||||
field=models.OneToOneField(blank=True, help_text='Обязательно для курьерской доставки', null=True, on_delete=django.db.models.deletion.CASCADE, related_name='order', to='orders.address', verbose_name='Адрес доставки'),
|
||||
name='customer',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='orders', to='customers.customer', verbose_name='Клиент'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='order',
|
||||
name='modified_by',
|
||||
field=models.ForeignKey(blank=True, help_text='Последний пользователь, изменивший заказ', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='modified_orders', to=settings.AUTH_USER_MODEL, verbose_name='Изменен пользователем'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='order',
|
||||
name='pickup_warehouse',
|
||||
field=models.ForeignKey(blank=True, help_text='Обязательно для самовывоза', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='pickup_orders', to='inventory.warehouse', verbose_name='Склад для самовывоза'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalorderitem',
|
||||
name='order',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='orders.order', verbose_name='Заказ'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='delivery',
|
||||
name='order',
|
||||
field=models.OneToOneField(help_text='Заказ, к которому относится доставка', on_delete=django.db.models.deletion.CASCADE, related_name='delivery', to='orders.order', verbose_name='Заказ'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderitem',
|
||||
name='kit_snapshot',
|
||||
field=models.ForeignKey(blank=True, help_text='Хранит состав комплекта на момент оформления заказа', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_items', to='orders.kitsnapshot', verbose_name='Снимок комплекта'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderitem',
|
||||
name='order',
|
||||
@@ -68,6 +93,11 @@ class Migration(migrations.Migration):
|
||||
name='product_kit',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='order_items', to='products.productkit', verbose_name='Комплект товаров'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderitem',
|
||||
name='showcase',
|
||||
field=models.ForeignKey(blank=True, help_text='Витрина, с которой был продан товар', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_items', to='inventory.showcase', verbose_name='Витрина'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderstatus',
|
||||
name='created_by',
|
||||
@@ -89,14 +119,83 @@ class Migration(migrations.Migration):
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='orders.orderstatus', verbose_name='Статус заказа'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='payment',
|
||||
model_name='paymentmethod',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='payments_created', to=settings.AUTH_USER_MODEL, verbose_name='Принял платеж'),
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_payment_methods', to=settings.AUTH_USER_MODEL, verbose_name='Создано'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='recipient',
|
||||
index=models.Index(fields=['phone'], name='orders_reci_phone_735356_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='recipient',
|
||||
index=models.Index(fields=['name'], name='orders_reci_name_e52d5b_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='recipient',
|
||||
index=models.Index(fields=['created_at'], name='orders_reci_created_34a391_idx'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='payment',
|
||||
model_name='order',
|
||||
name='recipient',
|
||||
field=models.ForeignKey(blank=True, help_text='Заполняется, если покупатель не является получателем', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='orders', to='orders.recipient', verbose_name='Получатель'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalorder',
|
||||
name='recipient',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, help_text='Заполняется, если покупатель не является получателем', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='orders.recipient', verbose_name='Получатель'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transaction',
|
||||
name='created_by',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transactions_created', to=settings.AUTH_USER_MODEL, verbose_name='Создал'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transaction',
|
||||
name='order',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='payments', to='orders.order', verbose_name='Заказ'),
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transactions', to='orders.order', verbose_name='Заказ'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transaction',
|
||||
name='payment_method',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transactions', to='orders.paymentmethod', verbose_name='Способ оплаты/возврата'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='transaction',
|
||||
name='related_payment',
|
||||
field=models.ForeignKey(blank=True, help_text='Для возвратов - на какой платёж ссылается этот возврат', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='refunds', to='orders.transaction', verbose_name='Связанный платёж'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='kitsnapshot',
|
||||
index=models.Index(fields=['original_kit'], name='orders_kits_origina_f8d311_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='kitsnapshot',
|
||||
index=models.Index(fields=['created_at'], name='orders_kits_created_70de88_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='kititemsnapshot',
|
||||
index=models.Index(fields=['kit_snapshot'], name='orders_kiti_kit_sna_bf307e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='delivery',
|
||||
index=models.Index(fields=['delivery_type'], name='orders_deli_deliver_ac3dc8_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='delivery',
|
||||
index=models.Index(fields=['created_at'], name='orders_deli_created_1a3ff3_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='delivery',
|
||||
index=models.Index(fields=['delivery_date'], name='orders_deli_deliver_e898e4_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='delivery',
|
||||
index=models.Index(fields=['time_from'], name='orders_deli_time_fr_916f57_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='delivery',
|
||||
index=models.Index(fields=['time_to'], name='orders_deli_time_to_7f2573_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='orderitem',
|
||||
@@ -110,6 +209,14 @@ class Migration(migrations.Migration):
|
||||
model_name='orderitem',
|
||||
index=models.Index(fields=['product_kit'], name='orders_orde_product_925b51_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='orderitem',
|
||||
index=models.Index(fields=['is_from_showcase'], name='orders_orde_is_from_32d8f7_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='orderitem',
|
||||
index=models.Index(fields=['showcase'], name='orders_orde_showcas_aa97bd_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='orderstatus',
|
||||
index=models.Index(fields=['code'], name='orders_orde_code_5e1ef7_idx'),
|
||||
@@ -122,6 +229,18 @@ class Migration(migrations.Migration):
|
||||
model_name='orderstatus',
|
||||
index=models.Index(fields=['order'], name='orders_orde_order_2e2930_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymentmethod',
|
||||
index=models.Index(fields=['code'], name='orders_paym_code_f40d7e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymentmethod',
|
||||
index=models.Index(fields=['is_active'], name='orders_paym_is_acti_e2be69_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymentmethod',
|
||||
index=models.Index(fields=['order'], name='orders_paym_order_94e282_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='order',
|
||||
index=models.Index(fields=['customer'], name='orders_orde_custome_59b6fb_idx'),
|
||||
@@ -130,14 +249,6 @@ class Migration(migrations.Migration):
|
||||
model_name='order',
|
||||
index=models.Index(fields=['status'], name='orders_orde_status__eb4f00_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='order',
|
||||
index=models.Index(fields=['delivery_date'], name='orders_orde_deliver_e4274f_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='order',
|
||||
index=models.Index(fields=['is_delivery'], name='orders_orde_is_deli_07c9c0_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='order',
|
||||
index=models.Index(fields=['payment_status'], name='orders_orde_payment_bc131d_idx'),
|
||||
@@ -151,15 +262,19 @@ class Migration(migrations.Migration):
|
||||
index=models.Index(fields=['order_number'], name='orders_orde_order_n_f3ada5_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='order',
|
||||
index=models.Index(fields=['is_custom_delivery_cost'], name='orders_orde_is_cust_108e98_idx'),
|
||||
model_name='transaction',
|
||||
index=models.Index(fields=['order', '-transaction_date'], name='orders_tran_order_i_dc90ee_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='payment',
|
||||
index=models.Index(fields=['order'], name='orders_paym_order_i_8c8d98_idx'),
|
||||
model_name='transaction',
|
||||
index=models.Index(fields=['transaction_type'], name='orders_tran_transac_3d971d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='payment',
|
||||
index=models.Index(fields=['payment_date'], name='orders_paym_payment_9e5ac0_idx'),
|
||||
model_name='transaction',
|
||||
index=models.Index(fields=['payment_method'], name='orders_tran_payment_7e354c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='transaction',
|
||||
index=models.Index(fields=['transaction_date'], name='orders_tran_transac_1bae48_idx'),
|
||||
),
|
||||
]
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-16 18:07
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('inventory', '0003_showcase_reservation_showcase_and_more'),
|
||||
('orders', '0002_initial'),
|
||||
('products', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='historicalorderitem',
|
||||
name='is_from_showcase',
|
||||
field=models.BooleanField(default=False, help_text='True если товар продан с витрины', verbose_name='С витрины'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalorderitem',
|
||||
name='showcase',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, help_text='Витрина, с которой был продан товар', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='inventory.showcase', verbose_name='Витрина'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderitem',
|
||||
name='is_from_showcase',
|
||||
field=models.BooleanField(default=False, help_text='True если товар продан с витрины', verbose_name='С витрины'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderitem',
|
||||
name='showcase',
|
||||
field=models.ForeignKey(blank=True, help_text='Витрина, с которой был продан товар', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_items', to='inventory.showcase', verbose_name='Витрина'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='orderitem',
|
||||
index=models.Index(fields=['is_from_showcase'], name='orders_orde_is_from_32d8f7_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='orderitem',
|
||||
index=models.Index(fields=['showcase'], name='orders_orde_showcas_aa97bd_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,61 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-26 08:06
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0003_historicalorderitem_is_from_showcase_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='historicalorder',
|
||||
name='payment_method',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='order',
|
||||
name='payment_method',
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='PaymentMethod',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('code', models.SlugField(help_text="Уникальный идентификатор (например: 'cash_to_courier', 'card_to_courier')", unique=True, verbose_name='Код способа оплаты')),
|
||||
('name', models.CharField(max_length=100, verbose_name='Название способа оплаты')),
|
||||
('description', models.TextField(blank=True, help_text='Дополнительная информация о способе оплаты', verbose_name='Описание')),
|
||||
('is_active', models.BooleanField(default=True, help_text='Отключенные способы оплаты не отображаются при создании заказа', verbose_name='Активен')),
|
||||
('order', models.PositiveIntegerField(default=0, verbose_name='Порядок отображения')),
|
||||
('is_system', models.BooleanField(default=False, help_text='Системные способы оплаты нельзя удалить через интерфейс', verbose_name='Системный')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||
('updated_at', models.DateTimeField(auto_now=True)),
|
||||
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_payment_methods', to=settings.AUTH_USER_MODEL, verbose_name='Создано')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Способ оплаты',
|
||||
'verbose_name_plural': 'Способы оплаты',
|
||||
'ordering': ['order', 'name'],
|
||||
},
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='payment',
|
||||
name='payment_method',
|
||||
field=models.ForeignKey(help_text='Способ оплаты данного платежа', on_delete=django.db.models.deletion.PROTECT, related_name='payments', to='orders.paymentmethod', verbose_name='Способ оплаты'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymentmethod',
|
||||
index=models.Index(fields=['code'], name='orders_paym_code_f40d7e_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymentmethod',
|
||||
index=models.Index(fields=['is_active'], name='orders_paym_is_acti_e2be69_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='paymentmethod',
|
||||
index=models.Index(fields=['order'], name='orders_paym_order_94e282_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,21 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-28 23:00
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0004_refactor_models_and_add_payment_method'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='historicalorder',
|
||||
name='discount_amount',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='order',
|
||||
name='discount_amount',
|
||||
),
|
||||
]
|
||||
@@ -1,55 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-11-29 09:42
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0005_remove_historicalorder_discount_amount_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Transaction',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('transaction_type', models.CharField(choices=[('payment', 'Платёж'), ('refund', 'Возврат')], default='payment', max_length=20, verbose_name='Тип транзакции')),
|
||||
('amount', models.DecimalField(decimal_places=2, help_text="Всегда положительная. Для возврата используется transaction_type='refund'", max_digits=10, verbose_name='Сумма')),
|
||||
('transaction_date', models.DateTimeField(auto_now_add=True, verbose_name='Дата и время транзакции')),
|
||||
('notes', models.TextField(blank=True, null=True, verbose_name='Примечания')),
|
||||
('reason', models.CharField(blank=True, help_text='Причина возврата или особенности платежа', max_length=255, null=True, verbose_name='Причина')),
|
||||
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='transactions_created', to=settings.AUTH_USER_MODEL, verbose_name='Создал')),
|
||||
('order', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transactions', to='orders.order', verbose_name='Заказ')),
|
||||
('payment_method', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='transactions', to='orders.paymentmethod', verbose_name='Способ оплаты/возврата')),
|
||||
('related_payment', models.ForeignKey(blank=True, help_text='Для возвратов - на какой платёж ссылается этот возврат', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='refunds', to='orders.transaction', verbose_name='Связанный платёж')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Транзакция',
|
||||
'verbose_name_plural': 'Транзакции',
|
||||
'ordering': ['-transaction_date'],
|
||||
},
|
||||
),
|
||||
migrations.DeleteModel(
|
||||
name='Payment',
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='transaction',
|
||||
index=models.Index(fields=['order', '-transaction_date'], name='orders_tran_order_i_dc90ee_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='transaction',
|
||||
index=models.Index(fields=['transaction_type'], name='orders_tran_transac_3d971d_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='transaction',
|
||||
index=models.Index(fields=['payment_method'], name='orders_tran_payment_7e354c_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='transaction',
|
||||
index=models.Index(fields=['transaction_date'], name='orders_tran_transac_1bae48_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,76 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-17 07:50
|
||||
|
||||
import django.db.models.deletion
|
||||
from decimal import Decimal
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0006_transaction_delete_payment_and_more'),
|
||||
('products', '0010_alter_product_cost_price'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='KitSnapshot',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(max_length=200, verbose_name='Название')),
|
||||
('sku', models.CharField(blank=True, max_length=100, verbose_name='Артикул')),
|
||||
('description', models.TextField(blank=True, verbose_name='Описание')),
|
||||
('base_price', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=10, verbose_name='Базовая цена')),
|
||||
('price', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=10, verbose_name='Итоговая цена')),
|
||||
('sale_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True, verbose_name='Цена со скидкой')),
|
||||
('price_adjustment_type', models.CharField(default='none', max_length=20, verbose_name='Тип корректировки')),
|
||||
('price_adjustment_value', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=10, verbose_name='Значение корректировки')),
|
||||
('is_temporary', models.BooleanField(default=False, verbose_name='Временный комплект')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('original_kit', models.ForeignKey(blank=True, help_text='Ссылка на комплект, с которого создан снимок', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='snapshots', to='products.productkit', verbose_name='Оригинальный комплект')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Снимок комплекта',
|
||||
'verbose_name_plural': 'Снимки комплектов',
|
||||
'ordering': ['-created_at'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='KitItemSnapshot',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('product_name', models.CharField(blank=True, max_length=200, verbose_name='Название товара')),
|
||||
('product_sku', models.CharField(blank=True, max_length=100, verbose_name='Артикул товара')),
|
||||
('product_price', models.DecimalField(decimal_places=2, default=Decimal('0'), max_digits=10, verbose_name='Цена товара')),
|
||||
('variant_group_name', models.CharField(blank=True, max_length=200, verbose_name='Группа вариантов')),
|
||||
('quantity', models.DecimalField(decimal_places=3, max_digits=10, verbose_name='Количество')),
|
||||
('kit_snapshot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='items', to='orders.kitsnapshot', verbose_name='Снимок комплекта')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Снимок компонента',
|
||||
'verbose_name_plural': 'Снимки компонентов',
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalorderitem',
|
||||
name='kit_snapshot',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, help_text='Хранит состав комплекта на момент оформления заказа', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='orders.kitsnapshot', verbose_name='Снимок комплекта'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderitem',
|
||||
name='kit_snapshot',
|
||||
field=models.ForeignKey(blank=True, help_text='Хранит состав комплекта на момент оформления заказа', null=True, on_delete=django.db.models.deletion.PROTECT, related_name='order_items', to='orders.kitsnapshot', verbose_name='Снимок комплекта'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='kitsnapshot',
|
||||
index=models.Index(fields=['original_kit'], name='orders_kits_origina_f8d311_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='kitsnapshot',
|
||||
index=models.Index(fields=['created_at'], name='orders_kits_created_70de88_idx'),
|
||||
),
|
||||
migrations.AddIndex(
|
||||
model_name='kititemsnapshot',
|
||||
index=models.Index(fields=['kit_snapshot'], name='orders_kiti_kit_sna_bf307e_idx'),
|
||||
),
|
||||
]
|
||||
@@ -1,39 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-17 11:58
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0007_kit_snapshots'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='historicalorderitem',
|
||||
name='item_name_snapshot',
|
||||
field=models.CharField(default='', max_length=200, verbose_name='Название на момент заказа'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalorderitem',
|
||||
name='item_sku_snapshot',
|
||||
field=models.CharField(blank=True, max_length=100, verbose_name='Артикул на момент заказа'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderitem',
|
||||
name='item_name_snapshot',
|
||||
field=models.CharField(default='', max_length=200, verbose_name='Название на момент заказа'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='orderitem',
|
||||
name='item_sku_snapshot',
|
||||
field=models.CharField(blank=True, max_length=100, verbose_name='Артикул на момент заказа'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='orderitem',
|
||||
name='kit_snapshot',
|
||||
field=models.ForeignKey(blank=True, help_text='Хранит состав комплекта на момент оформления заказа', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='order_items', to='orders.kitsnapshot', verbose_name='Снимок комплекта'),
|
||||
),
|
||||
]
|
||||
@@ -1,20 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-17 18:37
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0008_add_item_snapshots'),
|
||||
('products', '0010_alter_product_cost_price'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='kititemsnapshot',
|
||||
name='original_product',
|
||||
field=models.ForeignKey(blank=True, help_text='Ссылка на товар для резервирования на складе', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='kit_item_snapshots', to='products.product', verbose_name='Оригинальный товар'),
|
||||
),
|
||||
]
|
||||
@@ -1,69 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-22 19:32
|
||||
|
||||
import django.db.models.deletion
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0009_add_original_product_to_kit_item_snapshot'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='address',
|
||||
name='recipient_name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='address',
|
||||
name='recipient_phone',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='historicalorder',
|
||||
name='recipient_name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='historicalorder',
|
||||
name='recipient_phone',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='order',
|
||||
name='recipient_name',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='order',
|
||||
name='recipient_phone',
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='order',
|
||||
name='delivery_address',
|
||||
field=models.ForeignKey(blank=True, help_text='Обязательно для курьерской доставки', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='orders', to='orders.address', verbose_name='Адрес доставки'),
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Recipient',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('name', models.CharField(help_text='ФИО или название организации получателя', max_length=200, verbose_name='Имя получателя')),
|
||||
('phone', models.CharField(help_text='Контактный телефон для связи с получателем', max_length=20, verbose_name='Телефон получателя')),
|
||||
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
||||
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
|
||||
],
|
||||
options={
|
||||
'verbose_name': 'Получатель',
|
||||
'verbose_name_plural': 'Получатели',
|
||||
'ordering': ['-created_at'],
|
||||
'indexes': [models.Index(fields=['phone'], name='orders_reci_phone_735356_idx'), models.Index(fields=['name'], name='orders_reci_name_e52d5b_idx'), models.Index(fields=['created_at'], name='orders_reci_created_34a391_idx')],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='historicalorder',
|
||||
name='recipient',
|
||||
field=models.ForeignKey(blank=True, db_constraint=False, help_text='Заполняется, если покупатель не является получателем', null=True, on_delete=django.db.models.deletion.DO_NOTHING, related_name='+', to='orders.recipient', verbose_name='Получатель'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='order',
|
||||
name='recipient',
|
||||
field=models.ForeignKey(blank=True, help_text='Заполняется, если покупатель не является получателем', null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='orders', to='orders.recipient', verbose_name='Получатель'),
|
||||
),
|
||||
]
|
||||
@@ -1,77 +0,0 @@
|
||||
# Generated by Django 5.0.10 on 2025-12-22 19:32
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def migrate_recipient_data_forward(apps, schema_editor):
|
||||
"""
|
||||
Перенос данных получателей из старых полей Order в новую модель Recipient.
|
||||
Так как поля recipient_name и recipient_phone уже удалены,
|
||||
мы используем HistoricalOrder для восстановления данных.
|
||||
"""
|
||||
# Получаем модели
|
||||
HistoricalOrder = apps.get_model('orders', 'HistoricalOrder')
|
||||
Recipient = apps.get_model('orders', 'Recipient')
|
||||
Order = apps.get_model('orders', 'Order')
|
||||
|
||||
# Словарь для кэширования recipient'ов
|
||||
recipients_cache = {}
|
||||
|
||||
# Обрабатываем каждый заказ
|
||||
for order in Order.objects.all():
|
||||
# Находим последнюю историческую запись для этого заказа
|
||||
hist = HistoricalOrder.objects.filter(
|
||||
order_number=order.order_number
|
||||
).order_by('-history_date').first()
|
||||
|
||||
if not hist:
|
||||
continue
|
||||
|
||||
# Проверяем, есть ли данные получателя
|
||||
recipient_name = getattr(hist, 'recipient_name', None)
|
||||
recipient_phone = getattr(hist, 'recipient_phone', None)
|
||||
|
||||
# Если получатель не указан или customer_is_recipient=True, пропускаем
|
||||
if not recipient_name or not recipient_phone or order.customer_is_recipient:
|
||||
continue
|
||||
|
||||
# Создаем ключ для кэша
|
||||
cache_key = f"{recipient_name}|{recipient_phone}"
|
||||
|
||||
# Проверяем, есть ли уже такой получатель в кэше
|
||||
if cache_key in recipients_cache:
|
||||
recipient = recipients_cache[cache_key]
|
||||
else:
|
||||
# Создаем нового получателя
|
||||
recipient, created = Recipient.objects.get_or_create(
|
||||
name=recipient_name,
|
||||
phone=recipient_phone
|
||||
)
|
||||
recipients_cache[cache_key] = recipient
|
||||
|
||||
# Привязываем получателя к заказу
|
||||
order.recipient = recipient
|
||||
order.save(update_fields=['recipient'])
|
||||
|
||||
|
||||
def migrate_recipient_data_backward(apps, schema_editor):
|
||||
"""
|
||||
Обратная миграция - просто очищаем recipient поле в Order.
|
||||
Данные вернутся из HistoricalOrder при повторном apply.
|
||||
"""
|
||||
Order = apps.get_model('orders', 'Order')
|
||||
Order.objects.all().update(recipient=None)
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('orders', '0010_remove_address_recipient_name_and_more'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(
|
||||
migrate_recipient_data_forward,
|
||||
migrate_recipient_data_backward
|
||||
),
|
||||
]
|
||||
Reference in New Issue
Block a user