From 8d7869e9e7fc05945e895d6b119028db14eda95d Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Thu, 11 Dec 2025 21:52:09 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D1=81=D1=82=D0=B0=D1=82=D1=83=D1=81=20'converted=5Fto?= =?UTF-8?q?=5Fwriteoff'=20=D0=B4=D0=BB=D1=8F=20=D1=80=D0=B5=D0=B7=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=BE=D0=B2=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=BE=D0=B2=20=D1=81=D0=BF=D0=B8=D1=81=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Проблема: - Резервы документов списания помечались как '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 - Защита от ошибок при будущих доработках (откат списания) --- ...0011_add_writeoff_status_to_reservation.py | 23 +++++++++++++++++++ myproject/inventory/models.py | 6 +++-- .../services/writeoff_document_service.py | 4 ++-- 3 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 myproject/inventory/migrations/0011_add_writeoff_status_to_reservation.py diff --git a/myproject/inventory/migrations/0011_add_writeoff_status_to_reservation.py b/myproject/inventory/migrations/0011_add_writeoff_status_to_reservation.py new file mode 100644 index 0000000..c69845d --- /dev/null +++ b/myproject/inventory/migrations/0011_add_writeoff_status_to_reservation.py @@ -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='Статус'), + ), + ] diff --git a/myproject/inventory/models.py b/myproject/inventory/models.py index 343f739..00fcbdb 100644 --- a/myproject/inventory/models.py +++ b/myproject/inventory/models.py @@ -404,6 +404,7 @@ class Reservation(models.Model): ('reserved', 'Зарезервирован'), ('released', 'Освобожден'), ('converted_to_sale', 'Преобразован в продажу'), + ('converted_to_writeoff', 'Преобразован в списание'), ] 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, related_name='reservations', 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="Статус") reserved_at = models.DateTimeField(auto_now_add=True, verbose_name="Дата резервирования") released_at = models.DateTimeField(null=True, blank=True, verbose_name="Дата освобождения") converted_at = models.DateTimeField(null=True, blank=True, - verbose_name="Дата преобразования в продажу") + verbose_name="Дата преобразования", + help_text="Дата преобразования в продажу или списание") # Soft Lock для корзины POS (витринные комплекты) cart_lock_expires_at = models.DateTimeField( diff --git a/myproject/inventory/services/writeoff_document_service.py b/myproject/inventory/services/writeoff_document_service.py index 35555f1..201fc6e 100644 --- a/myproject/inventory/services/writeoff_document_service.py +++ b/myproject/inventory/services/writeoff_document_service.py @@ -281,9 +281,9 @@ class WriteOffDocumentService: f"Не хватает: {remaining}" ) - # Обновляем резерв + # Обновляем резерв - помечаем как преобразованный в списание if item.reservation: - item.reservation.status = 'converted_to_sale' + item.reservation.status = 'converted_to_writeoff' item.reservation.converted_at = timezone.now() item.reservation.save(update_fields=['status', 'converted_at'])