Добавлен статус 'converted_to_writeoff' для резервов документов списания
Проблема: - Резервы документов списания помечались как 'converted_to_sale' - Это вводило в заблуждение - списание это не продажа - В админке резервы списания отображались как 'В продажу' Решение: - Добавлен новый статус 'converted_to_writeoff' в Reservation.STATUS_CHOICES - Увеличен max_length поля status с 20 до 25 символов - Обновлен WriteOffDocumentService.confirm_document() - теперь использует новый статус - Обновлено описание поля converted_at (теперь для продажи ИЛИ списания) - Создана миграция 0011_add_writeoff_status_to_reservation Изменения: - inventory/models.py: добавлен статус, увеличен max_length, обновлен help_text - inventory/services/writeoff_document_service.py: используется converted_to_writeoff - inventory/migrations/0011_*.py: миграция для изменений модели Влияние: - Чистая аналитика: можно отличить продажи от списаний - Корректный учёт Stock: статус влияет на quantity_reserved - Защита от ошибок при будущих доработках (откат списания)
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2025-12-11 18:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('inventory', '0010_writeoff_document'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='reservation',
|
||||||
|
name='converted_at',
|
||||||
|
field=models.DateTimeField(blank=True, help_text='Дата преобразования в продажу или списание', null=True, verbose_name='Дата преобразования'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='reservation',
|
||||||
|
name='status',
|
||||||
|
field=models.CharField(choices=[('reserved', 'Зарезервирован'), ('released', 'Освобожден'), ('converted_to_sale', 'Преобразован в продажу'), ('converted_to_writeoff', 'Преобразован в списание')], default='reserved', max_length=25, verbose_name='Статус'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -404,6 +404,7 @@ class Reservation(models.Model):
|
|||||||
('reserved', 'Зарезервирован'),
|
('reserved', 'Зарезервирован'),
|
||||||
('released', 'Освобожден'),
|
('released', 'Освобожден'),
|
||||||
('converted_to_sale', 'Преобразован в продажу'),
|
('converted_to_sale', 'Преобразован в продажу'),
|
||||||
|
('converted_to_writeoff', 'Преобразован в списание'),
|
||||||
]
|
]
|
||||||
|
|
||||||
order_item = models.ForeignKey('orders.OrderItem', on_delete=models.CASCADE,
|
order_item = models.ForeignKey('orders.OrderItem', on_delete=models.CASCADE,
|
||||||
@@ -422,12 +423,13 @@ class Reservation(models.Model):
|
|||||||
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE,
|
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE,
|
||||||
related_name='reservations', verbose_name="Склад")
|
related_name='reservations', verbose_name="Склад")
|
||||||
quantity = models.DecimalField(max_digits=10, decimal_places=3, verbose_name="Количество")
|
quantity = models.DecimalField(max_digits=10, decimal_places=3, verbose_name="Количество")
|
||||||
status = models.CharField(max_length=20, choices=STATUS_CHOICES,
|
status = models.CharField(max_length=25, choices=STATUS_CHOICES,
|
||||||
default='reserved', verbose_name="Статус")
|
default='reserved', verbose_name="Статус")
|
||||||
reserved_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата резервирования")
|
reserved_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата резервирования")
|
||||||
released_at = models.DateTimeField(null=True, blank=True, verbose_name="Дата освобождения")
|
released_at = models.DateTimeField(null=True, blank=True, verbose_name="Дата освобождения")
|
||||||
converted_at = models.DateTimeField(null=True, blank=True,
|
converted_at = models.DateTimeField(null=True, blank=True,
|
||||||
verbose_name="Дата преобразования в продажу")
|
verbose_name="Дата преобразования",
|
||||||
|
help_text="Дата преобразования в продажу или списание")
|
||||||
|
|
||||||
# Soft Lock для корзины POS (витринные комплекты)
|
# Soft Lock для корзины POS (витринные комплекты)
|
||||||
cart_lock_expires_at = models.DateTimeField(
|
cart_lock_expires_at = models.DateTimeField(
|
||||||
|
|||||||
@@ -281,9 +281,9 @@ class WriteOffDocumentService:
|
|||||||
f"Не хватает: {remaining}"
|
f"Не хватает: {remaining}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Обновляем резерв
|
# Обновляем резерв - помечаем как преобразованный в списание
|
||||||
if item.reservation:
|
if item.reservation:
|
||||||
item.reservation.status = 'converted_to_sale'
|
item.reservation.status = 'converted_to_writeoff'
|
||||||
item.reservation.converted_at = timezone.now()
|
item.reservation.converted_at = timezone.now()
|
||||||
item.reservation.save(update_fields=['status', 'converted_at'])
|
item.reservation.save(update_fields=['status', 'converted_at'])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user