diff --git a/myproject/inventory/admin.py b/myproject/inventory/admin.py index 97b3ea1..f677ffd 100644 --- a/myproject/inventory/admin.py +++ b/myproject/inventory/admin.py @@ -7,7 +7,7 @@ from decimal import Decimal from inventory.models import ( Warehouse, StockBatch, Incoming, IncomingBatch, Sale, WriteOff, Transfer, Inventory, InventoryLine, Reservation, Stock, StockMovement, - SaleBatchAllocation, Showcase + SaleBatchAllocation, Showcase, WriteOffDocument, WriteOffDocumentItem ) @@ -356,3 +356,61 @@ class StockMovementAdmin(admin.ModelAdmin): search_fields = ('product__name', 'order__order_number') date_hierarchy = 'created_at' readonly_fields = ('created_at',) + + +# ===== WRITEOFF DOCUMENT (документы списания) ===== +class WriteOffDocumentItemInline(admin.TabularInline): + model = WriteOffDocumentItem + extra = 0 + fields = ('product', 'quantity', 'reason', 'notes', 'reservation') + readonly_fields = ('reservation',) + raw_id_fields = ('product',) + + +@admin.register(WriteOffDocument) +class WriteOffDocumentAdmin(admin.ModelAdmin): + list_display = ('document_number', 'warehouse', 'status_display', 'date', 'items_count', 'total_quantity_display', 'created_by', 'created_at') + list_filter = ('status', 'warehouse', 'date', 'created_at') + search_fields = ('document_number', 'warehouse__name') + date_hierarchy = 'date' + readonly_fields = ('document_number', 'created_at', 'updated_at', 'confirmed_at', 'confirmed_by') + inlines = [WriteOffDocumentItemInline] + + fieldsets = ( + ('Документ', { + 'fields': ('document_number', 'warehouse', 'status', 'date', 'notes') + }), + ('Аудит', { + 'fields': ('created_by', 'created_at', 'confirmed_by', 'confirmed_at', 'updated_at'), + 'classes': ('collapse',) + }), + ) + + def status_display(self, obj): + colors = { + 'draft': '#ff9900', + 'confirmed': '#008000', + 'cancelled': '#ff0000', + } + return format_html( + '{}', + colors.get(obj.status, '#000000'), + obj.get_status_display() + ) + status_display.short_description = 'Статус' + + def items_count(self, obj): + return obj.items.count() + items_count.short_description = 'Позиций' + + def total_quantity_display(self, obj): + return f"{obj.total_quantity} шт" + total_quantity_display.short_description = 'Всего' + + +@admin.register(WriteOffDocumentItem) +class WriteOffDocumentItemAdmin(admin.ModelAdmin): + list_display = ('document', 'product', 'quantity', 'reason', 'created_at') + list_filter = ('reason', 'document__status', 'created_at') + search_fields = ('product__name', 'document__document_number') + raw_id_fields = ('product', 'document', 'reservation') diff --git a/myproject/inventory/signals.py b/myproject/inventory/signals.py index f15411d..5bfae3b 100644 --- a/myproject/inventory/signals.py +++ b/myproject/inventory/signals.py @@ -11,7 +11,7 @@ from django.utils import timezone from decimal import Decimal from orders.models import Order, OrderItem -from inventory.models import Reservation, Warehouse, Incoming, StockBatch, Sale, SaleBatchAllocation, Inventory, WriteOff, Stock +from inventory.models import Reservation, Warehouse, Incoming, StockBatch, Sale, SaleBatchAllocation, Inventory, WriteOff, Stock, WriteOffDocumentItem from inventory.services import SaleProcessor from inventory.services.batch_manager import StockBatchManager from inventory.services.inventory_processor import InventoryProcessor @@ -1357,3 +1357,19 @@ def update_kit_prices_on_product_change(sender, instance, created, **kwargs): f"после изменения цены товара {instance.sku}: {e}", exc_info=True ) + + +# ==================== WRITEOFF DOCUMENT SIGNALS ==================== + +@receiver(pre_delete, sender=WriteOffDocumentItem) +def release_reservation_on_writeoff_item_delete(sender, instance, **kwargs): + """ + Сигнал: При удалении позиции документа списания освобождаем связанный резерв. + + Это fallback для случаев удаления напрямую через ORM/Admin, + минуя WriteOffDocumentService.remove_item(). + """ + if instance.reservation and instance.reservation.status == 'reserved': + instance.reservation.status = 'released' + instance.reservation.released_at = timezone.now() + instance.reservation.save(update_fields=['status', 'released_at'])