refactor: Заменить сущность Магазин (Shop) на Склад (Warehouse)

Упрощена логика системы путём замены отдельной сущности "Магазин"
на универсальную сущность "Склад", которая может использоваться
как точка самовывоза.

Изменения:
- Расширена модель Warehouse: добавлены адрес, контакты, флаг is_pickup_point
- Модель Order: поле pickup_shop заменено на pickup_warehouse
- Обновлены все формы, сервисы, views, admin для работы со складами
- Обновлены шаблоны HTML и JavaScript код
- Удалено приложение shops полностью
- Пересозданы миграции БД
- Обновлён навбар (удалена ссылка на магазины)

Преимущества:
- Упрощена архитектура системы
- Единая точка управления складами и точками самовывоза
- Интеграция с системой инвентаризации

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-14 23:50:30 +03:00
parent d3ac875a0e
commit 4a4bd437b9
37 changed files with 99 additions and 740 deletions

View File

@@ -1,5 +1,6 @@
# Generated by Django 5.0.10 on 2025-11-13 13:12
# Generated by Django 5.0.10 on 2025-11-14 20:45
import phonenumber_field.modelfields
from django.db import migrations, models
@@ -216,8 +217,13 @@ class Migration(migrations.Migration):
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=200, verbose_name='Название')),
('description', models.TextField(blank=True, 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='Номер здания')),
('phone', phonenumber_field.modelfields.PhoneNumberField(blank=True, max_length=128, null=True, region='BY', verbose_name='Телефон')),
('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Email')),
('is_active', models.BooleanField(default=True, verbose_name='Активен')),
('is_default', models.BooleanField(default=False, help_text='Автоматически выбирается при создании новых документов', verbose_name='Склад по умолчанию')),
('is_pickup_point', models.BooleanField(default=True, help_text='Можно ли выбрать этот склад как точку самовывоза заказа', verbose_name='Доступен для самовывоза')),
('created_at', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
('updated_at', models.DateTimeField(auto_now=True, verbose_name='Дата обновления')),
],

View File

@@ -1,4 +1,4 @@
# Generated by Django 5.0.10 on 2025-11-13 13:12
# Generated by Django 5.0.10 on 2025-11-14 20:45
import django.db.models.deletion
from django.db import migrations, models
@@ -128,6 +128,10 @@ class Migration(migrations.Migration):
model_name='warehouse',
index=models.Index(fields=['is_default'], name='inventory_w_is_defa_4b7615_idx'),
),
migrations.AddIndex(
model_name='warehouse',
index=models.Index(fields=['is_pickup_point'], name='inventory_w_is_pick_e86268_idx'),
),
migrations.AddField(
model_name='transferbatch',
name='from_warehouse',

View File

@@ -3,20 +3,38 @@ from django.utils import timezone
from django.core.exceptions import ValidationError
from decimal import Decimal
from products.models import Product
from phonenumber_field.modelfields import PhoneNumberField
class Warehouse(models.Model):
"""
Склад (физическое или логическое место хранения).
Может использоваться как точка самовывоза для заказов.
"""
name = models.CharField(max_length=200, verbose_name="Название")
description = models.TextField(blank=True, null=True, verbose_name="Описание")
# Адрес
street = models.CharField(max_length=255, blank=True, null=True, verbose_name="Улица")
building_number = models.CharField(max_length=20, blank=True, null=True, verbose_name="Номер здания")
# Контакты
phone = PhoneNumberField(region='BY', blank=True, null=True, verbose_name="Телефон")
email = models.EmailField(blank=True, null=True, verbose_name="Email")
# Настройки
is_active = models.BooleanField(default=True, verbose_name="Активен")
is_default = models.BooleanField(
default=False,
verbose_name="Склад по умолчанию",
help_text="Автоматически выбирается при создании новых документов"
)
is_pickup_point = models.BooleanField(
default=True,
verbose_name="Доступен для самовывоза",
help_text="Можно ли выбрать этот склад как точку самовывоза заказа"
)
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата создания")
updated_at = models.DateTimeField(auto_now=True, verbose_name="Дата обновления")
@@ -26,11 +44,24 @@ class Warehouse(models.Model):
indexes = [
models.Index(fields=['is_active']),
models.Index(fields=['is_default']),
models.Index(fields=['is_pickup_point']),
]
def __str__(self):
if self.street and self.building_number:
return f"{self.name} ({self.street}, {self.building_number})"
return self.name
@property
def full_address(self):
"""Полный адрес склада"""
parts = []
if self.street:
parts.append(self.street)
if self.building_number:
parts.append(self.building_number)
return ', '.join(parts) if parts else "Адрес не указан"
def save(self, *args, **kwargs):
"""Обеспечиваем что только один склад может быть по умолчанию в рамках одного тенанта"""
if self.is_default: