Добавлена поддержка документов списания в админке и сигналах
- Зарегистрированы модели WriteOffDocument и WriteOffDocumentItem в админке - Настроен inline для позиций документа в админке - Добавлены цветовые индикаторы статусов документа - Настроены фильтры, поиск и сортировка для удобной работы - Добавлен сигнал release_reservation_on_writeoff_item_delete - Автоматическое освобождение резервов при удалении позиций через админку - Защита от утечки резервов при прямом удалении через ORM
This commit is contained in:
@@ -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(
|
||||
'<span style="color: {}; font-weight: bold;">{}</span>',
|
||||
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')
|
||||
|
||||
@@ -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'])
|
||||
|
||||
Reference in New Issue
Block a user