import os from django.contrib import admin, messages from shiftflow.services.sessions import close_cutting_session from warehouse.models import StockItem from .models import ( Company, CuttingSession, Deal, DealItem, DxfPreviewJob, DxfPreviewSettings, EmployeeProfile, Item, Machine, MaterialRequirement, ProductionReportConsumption, ProductionReportRemnant, ProductionTask, ShiftItem, Workshop, ) # --- Настройка отображения Компаний --- @admin.register(Company) class CompanyAdmin(admin.ModelAdmin): """ Панель администрирования Компаний """ list_display = ('name', 'description') # Что видим в общем списке search_fields = ('name',) # Поиск по имени class DealItemInline(admin.TabularInline): model = DealItem fields = ('entity', 'quantity') autocomplete_fields = ('entity',) extra = 10 # --- Настройка отображения Сделок --- @admin.register(Deal) class DealAdmin(admin.ModelAdmin): """ Панель администрирования Сделок """ list_display = ('number', 'id', 'status', 'company') list_display_links = ('number',) search_fields = ('number', 'company__name') list_filter = ('status', 'company') inlines = (DealItemInline,) # --- Задания на производство (База) --- """ Панель администрирования Заданий на производство """ @admin.register(ProductionTask) class ProductionTaskAdmin(admin.ModelAdmin): list_display = ('drawing_name', 'deal', 'entity', 'material', 'quantity_ordered', 'created_at') search_fields = ('drawing_name', 'deal__number', 'entity__name', 'entity__drawing_number') list_filter = ('deal', 'material', 'is_bend') autocomplete_fields = ('deal', 'entity', 'material') """ Панель администрирования Сделочных элементов """ @admin.register(DealItem) class DealItemAdmin(admin.ModelAdmin): """ Панель администрирования Сделочных элементов """ list_display = ('deal', 'entity', 'quantity') search_fields = ('deal__number', 'entity__name', 'entity__drawing_number') list_filter = ('deal',) autocomplete_fields = ('deal', 'entity') @admin.register(MaterialRequirement) class MaterialRequirementAdmin(admin.ModelAdmin): """ Панель администрирования Требований к Материалам """ list_display = ('deal', 'material', 'required_qty', 'unit', 'status') search_fields = ('deal__number', 'material__name', 'material__full_name') list_filter = ('status', 'unit', 'material__category') autocomplete_fields = ('deal', 'material') """ Панель администрирования Сменных задания (Выполнение) """ @admin.register(Item) class ItemAdmin(admin.ModelAdmin): # Что видим в общем списке (используем task__ для доступа к полям базы) list_display = ('date', 'machine', 'get_deal', 'get_drawing', 'quantity_plan', 'quantity_fact', 'status', 'is_synced_1c') # Фильтры справа list_filter = ('date', 'machine', 'status', 'is_synced_1c', 'task__deal') # Поиск по номеру сделки и названию детали через связь task search_fields = ('task__drawing_name', 'task__deal__number') # Группируем поля в форме редактирования fieldsets = ( ('Связь с заданием', { 'fields': ('task', 'date', 'machine') }), ('Исполнение', { 'fields': ('quantity_plan', 'quantity_fact', 'status', 'is_synced_1c') }), ('Отходы и материалы', { 'fields': ('material_taken', 'usable_waste', 'scrap_weight') }), ) def get_deal(self, obj): return obj.task.deal if obj.task else "-" get_deal.short_description = 'Сделка' def get_drawing(self, obj): return obj.task.drawing_name if obj.task else "-" get_drawing.short_description = 'Деталь' @admin.register(Workshop) class WorkshopAdmin(admin.ModelAdmin): list_display = ('name', 'location') search_fields = ('name',) list_filter = ('location',) @admin.register(Machine) class MachineAdmin(admin.ModelAdmin): list_display = ('name', 'machine_type', 'workshop', 'location') list_display_links = ('name',) list_filter = ('machine_type', 'workshop') search_fields = ('name',) fields = ('name', 'machine_type', 'workshop', 'location') class ProductionReportLineInline(admin.TabularInline): model = ShiftItem fk_name = 'session' fields = ('task', 'quantity_fact', 'material_substitution') extra = 5 class ProductionReportConsumptionInline(admin.TabularInline): model = ProductionReportConsumption fk_name = 'report' fields = ('stock_item', 'quantity') autocomplete_fields = ('stock_item',) extra = 3 def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'stock_item': report = getattr(request, '_production_report_obj', None) if report and getattr(report, 'machine_id', None): machine = report.machine work_location = None if getattr(machine, 'workshop_id', None) and getattr(machine.workshop, 'location_id', None): work_location = machine.workshop.location elif getattr(machine, 'location_id', None): work_location = machine.location if work_location: kwargs['queryset'] = StockItem.objects.filter(location=work_location, material__isnull=False) else: kwargs['queryset'] = StockItem.objects.none() return super().formfield_for_foreignkey(db_field, request, **kwargs) class ProductionReportRemnantInline(admin.TabularInline): model = ProductionReportRemnant fk_name = 'report' fields = ('material', 'quantity', 'current_length', 'current_width') autocomplete_fields = ('material',) extra = 3 @admin.register(CuttingSession) class CuttingSessionAdmin(admin.ModelAdmin): """ Панель администрирования Производственных отчетов. Ограничение по складу: - списание сырья доступно только со склада цеха выбранного станка. """ list_display = ('date', 'id', 'machine', 'operator', 'used_stock_item', 'is_closed') list_display_links = ('date',) list_filter = ('date', 'machine', 'is_closed') search_fields = ('operator__username',) actions = ('action_close_sessions',) inlines = (ProductionReportLineInline, ProductionReportConsumptionInline, ProductionReportRemnantInline) def get_form(self, request, obj=None, **kwargs): request._production_report_obj = obj return super().get_form(request, obj, **kwargs) def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'used_stock_item': report = getattr(request, '_production_report_obj', None) if report and getattr(report, 'machine_id', None): machine = report.machine work_location = None if getattr(machine, 'workshop_id', None) and getattr(machine.workshop, 'location_id', None): work_location = machine.workshop.location elif getattr(machine, 'location_id', None): work_location = machine.location if work_location: kwargs['queryset'] = StockItem.objects.filter(location=work_location, material__isnull=False) else: kwargs['queryset'] = StockItem.objects.none() return super().formfield_for_foreignkey(db_field, request, **kwargs) @admin.action(description='Закрыть производственный отчет') def action_close_sessions(self, request, queryset): ok = 0 skipped = 0 failed = 0 for s in queryset: try: if s.is_closed: skipped += 1 continue close_cutting_session(s.id) ok += 1 except Exception as e: failed += 1 self.message_user(request, f'Отчет id={s.id}: {e}', level=messages.ERROR) if ok: self.message_user(request, f'Закрыто: {ok}.', level=messages.SUCCESS) if skipped: self.message_user(request, f'Пропущено (уже закрыто): {skipped}.', level=messages.WARNING) if failed: self.message_user(request, f'Ошибок: {failed}.', level=messages.ERROR) @admin.register(ShiftItem) class ShiftItemAdmin(admin.ModelAdmin): list_display = ('session', 'task', 'quantity_fact', 'material_substitution') list_filter = ('material_substitution',) autocomplete_fields = ('session', 'task') class DxfPreviewSettingsAdmin(admin.ModelAdmin): list_display = ( 'line_color', 'lineweight_scaling', 'min_lineweight', 'keep_original_colors', 'per_task_timeout_sec', 'updated_at', ) @admin.register(DxfPreviewJob) class DxfPreviewJobAdmin(admin.ModelAdmin): list_display = ('id', 'status', 'created_by', 'processed', 'total', 'updated', 'errors', 'started_at', 'finished_at') list_filter = ('status',) search_fields = ('last_message',) @admin.register(EmployeeProfile) class EmployeeProfileAdmin(admin.ModelAdmin): list_display = ('user', 'role') filter_horizontal = ('machines',)