Files
MES_Core/shiftflow/admin.py
2026-04-13 07:36:57 +03:00

307 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
WorkItem,
DealEntityProgress,
)
_models_to_reregister = (
Company,
CuttingSession,
Deal,
DealItem,
DxfPreviewJob,
DxfPreviewSettings,
EmployeeProfile,
Item,
Machine,
MaterialRequirement,
ProductionReportConsumption,
ProductionReportRemnant,
ProductionTask,
ShiftItem,
Workshop,
WorkItem,
DealEntityProgress,
)
for _m in _models_to_reregister:
try:
admin.site.unregister(_m)
except Exception:
pass
# --- Настройка отображения Компаний ---
@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(WorkItem)
class WorkItemAdmin(admin.ModelAdmin):
list_display = ('date', 'deal', 'entity', 'operation', 'workshop', 'machine', 'quantity_plan', 'quantity_done', 'status')
list_filter = ('date', 'status', 'workshop', 'machine', 'operation')
search_fields = ('deal__number', 'entity__name', 'entity__drawing_number', 'operation__name', 'operation__code')
autocomplete_fields = ('deal', 'entity', 'operation', 'workshop', 'machine')
@admin.register(DealEntityProgress)
class DealEntityProgressAdmin(admin.ModelAdmin):
list_display = ('deal', 'entity', 'current_seq')
search_fields = ('deal__number', 'entity__name', 'entity__drawing_number')
autocomplete_fields = ('deal', 'entity')
@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', 'is_readonly')
filter_horizontal = ('machines', 'allowed_workshops')