Добавлена система трансформации товаров
Реализована полная система трансформации товаров (превращение одного товара в другой). Пример: белая гипсофила → крашеная гипсофила. Особенности реализации: - Резервирование входных товаров в статусе draft - FIFO списание входных товаров при проведении - Автоматический расчёт себестоимости выходных товаров - Возможность отмены как черновиков, так и проведённых трансформаций Модели (inventory/models.py): - Transformation: документ трансформации (draft/completed/cancelled) - TransformationInput: входные товары (списание) - TransformationOutput: выходные товары (оприходование) - Добавлен статус 'converted_to_transformation' в Reservation - Добавлен тип 'transformation' в DocumentCounter Бизнес-логика (inventory/services/transformation_service.py): - TransformationService с методами CRUD - Валидация наличия товаров - Автоматическая генерация номеров документов Сигналы (inventory/signals.py): - Автоматическое резервирование входных товаров - FIFO списание при проведении - Создание партий выходных товаров - Откат операций при отмене Интерфейс без Django Admin: - Список трансформаций (list.html) - Форма создания (form.html) - Детальный просмотр с добавлением товаров (detail.html) - Интеграция с компонентом поиска товаров - 8 views для полного CRUD + проведение/отмена Миграция: - 0003_alter_documentcounter_counter_type_and_more.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,8 @@ from inventory.models import (
|
||||
Warehouse, StockBatch, Incoming, IncomingBatch, Sale, WriteOff, Transfer,
|
||||
Inventory, InventoryLine, Reservation, Stock, StockMovement,
|
||||
SaleBatchAllocation, Showcase, WriteOffDocument, WriteOffDocumentItem,
|
||||
IncomingDocument, IncomingDocumentItem
|
||||
IncomingDocument, IncomingDocumentItem, Transformation, TransformationInput,
|
||||
TransformationOutput
|
||||
)
|
||||
|
||||
|
||||
@@ -512,3 +513,69 @@ class IncomingDocumentItemAdmin(admin.ModelAdmin):
|
||||
def total_cost_display(self, obj):
|
||||
return f"{obj.total_cost:.2f}"
|
||||
total_cost_display.short_description = 'Сумма'
|
||||
|
||||
|
||||
# ===== TRANSFORMATION =====
|
||||
|
||||
class TransformationInputInline(admin.TabularInline):
|
||||
model = TransformationInput
|
||||
extra = 1
|
||||
fields = ['product', 'quantity']
|
||||
autocomplete_fields = ['product']
|
||||
|
||||
|
||||
class TransformationOutputInline(admin.TabularInline):
|
||||
model = TransformationOutput
|
||||
extra = 1
|
||||
fields = ['product', 'quantity', 'stock_batch']
|
||||
autocomplete_fields = ['product']
|
||||
readonly_fields = ['stock_batch']
|
||||
|
||||
|
||||
@admin.register(Transformation)
|
||||
class TransformationAdmin(admin.ModelAdmin):
|
||||
list_display = ['document_number', 'warehouse', 'status_display', 'date', 'employee', 'inputs_count', 'outputs_count']
|
||||
list_filter = ['status', 'warehouse', 'date']
|
||||
search_fields = ['document_number', 'comment']
|
||||
readonly_fields = ['document_number', 'date', 'created_at', 'updated_at']
|
||||
inlines = [TransformationInputInline, TransformationOutputInline]
|
||||
autocomplete_fields = ['warehouse', 'employee']
|
||||
|
||||
fieldsets = (
|
||||
('Основная информация', {
|
||||
'fields': ('document_number', 'warehouse', 'status', 'employee')
|
||||
}),
|
||||
('Детали', {
|
||||
'fields': ('comment', 'date', 'created_at', 'updated_at')
|
||||
}),
|
||||
)
|
||||
|
||||
def save_model(self, request, obj, form, change):
|
||||
if not obj.pk:
|
||||
# Генерируем номер документа при создании
|
||||
from inventory.models import DocumentCounter
|
||||
next_num = DocumentCounter.get_next_value('transformation')
|
||||
obj.document_number = f"TR-{next_num:05d}"
|
||||
obj.employee = request.user
|
||||
super().save_model(request, obj, form, change)
|
||||
|
||||
def status_display(self, obj):
|
||||
colors = {
|
||||
'draft': '#6c757d',
|
||||
'completed': '#28a745',
|
||||
'cancelled': '#dc3545',
|
||||
}
|
||||
return format_html(
|
||||
'<span style="color: {}; font-weight: bold;">{}</span>',
|
||||
colors.get(obj.status, '#6c757d'),
|
||||
obj.get_status_display()
|
||||
)
|
||||
status_display.short_description = 'Статус'
|
||||
|
||||
def inputs_count(self, obj):
|
||||
return obj.inputs.count()
|
||||
inputs_count.short_description = 'Входов'
|
||||
|
||||
def outputs_count(self, obj):
|
||||
return obj.outputs.count()
|
||||
outputs_count.short_description = 'Выходов'
|
||||
|
||||
Reference in New Issue
Block a user