All checks were successful
Deploy MES Core / deploy (push) Successful in 10s
268 lines
11 KiB
Python
268 lines
11 KiB
Python
from datetime import datetime
|
||
|
||
from django.shortcuts import redirect
|
||
from django.urls import reverse_lazy
|
||
from django.views.generic import TemplateView, ListView, UpdateView
|
||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||
from django.utils import timezone
|
||
from .models import Item, Machine
|
||
|
||
# Класс главной страницы (роутер)
|
||
class IndexView(TemplateView):
|
||
template_name = 'shiftflow/landing.html'
|
||
|
||
def get(self, request, *args, **kwargs):
|
||
# Если юзер авторизован — сразу отправляем его в реестр
|
||
if request.user.is_authenticated:
|
||
return redirect('registry')
|
||
# Если нет — показываем кнопку "Войти"
|
||
return super().get(request, *args, **kwargs)
|
||
|
||
# Класс реестра деталей (защищен LoginRequiredMixin)
|
||
class RegistryView(LoginRequiredMixin, ListView):
|
||
model = Item
|
||
template_name = 'shiftflow/registry.html'
|
||
context_object_name = 'items'
|
||
|
||
def get_queryset(self):
|
||
queryset = Item.objects.select_related('task', 'task__deal', 'task__material', 'machine')
|
||
user = self.request.user
|
||
profile = getattr(user, 'profile', None)
|
||
role = profile.role if profile else 'operator'
|
||
filtered = self.request.GET.get('filtered')
|
||
|
||
# Станки
|
||
m_ids = self.request.GET.getlist('m_ids')
|
||
if filtered and role != 'operator' and not m_ids:
|
||
return queryset.none()
|
||
if m_ids:
|
||
queryset = queryset.filter(machine_id__in=m_ids)
|
||
|
||
# Статусы (+ агрегат "closed" = done+partial)
|
||
statuses = self.request.GET.getlist('statuses')
|
||
if filtered and not statuses:
|
||
return queryset.none()
|
||
if statuses:
|
||
expanded = []
|
||
for s in statuses:
|
||
if s == 'closed':
|
||
expanded += ['done', 'partial']
|
||
else:
|
||
expanded.append(s)
|
||
queryset = queryset.filter(status__in=expanded)
|
||
|
||
# Даты
|
||
start_date = self.request.GET.get('start_date')
|
||
end_date = self.request.GET.get('end_date')
|
||
if not filtered:
|
||
today = timezone.now().date()
|
||
queryset = queryset.filter(date=today, status__in=['work', 'leftover'])
|
||
else:
|
||
if start_date:
|
||
queryset = queryset.filter(date__gte=start_date)
|
||
if end_date:
|
||
queryset = queryset.filter(date__lte=end_date)
|
||
|
||
# Списание (1С)
|
||
is_synced = self.request.GET.get('is_synced')
|
||
if is_synced in ['0', '1']:
|
||
queryset = queryset.filter(is_synced_1c=bool(int(is_synced)))
|
||
|
||
# Ограничения по ролям
|
||
if role == 'operator':
|
||
user_machines = profile.machines.all() if profile else Machine.objects.none()
|
||
queryset = queryset.filter(machine__in=user_machines, status='work')
|
||
elif role == 'master' and not filtered:
|
||
queryset = queryset.filter(status='work')
|
||
|
||
return queryset.order_by('status', '-date', 'machine__name', 'task__deal__number')
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
user = self.request.user
|
||
profile = getattr(user, 'profile', None)
|
||
role = profile.role if profile else 'operator'
|
||
context['user_role'] = role
|
||
|
||
machines = Machine.objects.all()
|
||
context['machines'] = machines
|
||
filtered = self.request.GET.get('filtered')
|
||
|
||
if not filtered:
|
||
today_str = timezone.now().date().strftime('%Y-%m-%d')
|
||
context['start_date'] = today_str
|
||
context['end_date'] = today_str
|
||
context['selected_statuses'] = ['work', 'leftover']
|
||
context['selected_machines'] = [m.id for m in machines]
|
||
context['all_selected_machines'] = True
|
||
else:
|
||
context['selected_machines'] = [int(i) for i in self.request.GET.getlist('m_ids') if i.isdigit()]
|
||
context['selected_statuses'] = self.request.GET.getlist('statuses')
|
||
context['start_date'] = self.request.GET.get('start_date', '')
|
||
context['end_date'] = self.request.GET.get('end_date', '')
|
||
context['is_synced'] = self.request.GET.get('is_synced', '')
|
||
context['all_selected_machines'] = False
|
||
|
||
return context
|
||
|
||
|
||
class RegistryPrintView(LoginRequiredMixin, TemplateView):
|
||
template_name = 'shiftflow/registry_print.html'
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else 'operator'
|
||
if role not in ['admin', 'technologist', 'master']:
|
||
return redirect('registry')
|
||
return super().dispatch(request, *args, **kwargs)
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
|
||
profile = getattr(self.request.user, 'profile', None)
|
||
role = profile.role if profile else 'operator'
|
||
context['user_role'] = role
|
||
|
||
queryset = Item.objects.select_related('task', 'task__deal', 'task__material', 'task__material__category', 'machine')
|
||
filtered = self.request.GET.get('filtered')
|
||
|
||
m_ids = self.request.GET.getlist('m_ids')
|
||
if filtered and not m_ids:
|
||
queryset = queryset.none()
|
||
if m_ids:
|
||
queryset = queryset.filter(machine_id__in=m_ids)
|
||
|
||
statuses = self.request.GET.getlist('statuses')
|
||
if filtered and not statuses:
|
||
queryset = queryset.none()
|
||
if statuses:
|
||
expanded = []
|
||
for s in statuses:
|
||
if s == 'closed':
|
||
expanded += ['done', 'partial']
|
||
else:
|
||
expanded.append(s)
|
||
queryset = queryset.filter(status__in=expanded)
|
||
|
||
start_date = self.request.GET.get('start_date')
|
||
end_date = self.request.GET.get('end_date')
|
||
if not filtered:
|
||
today = timezone.now().date()
|
||
queryset = queryset.filter(date=today, status__in=['work', 'leftover'])
|
||
start_date = today.strftime('%Y-%m-%d')
|
||
end_date = start_date
|
||
else:
|
||
if start_date:
|
||
queryset = queryset.filter(date__gte=start_date)
|
||
if end_date:
|
||
queryset = queryset.filter(date__lte=end_date)
|
||
|
||
is_synced = self.request.GET.get('is_synced')
|
||
if is_synced in ['0', '1']:
|
||
queryset = queryset.filter(is_synced_1c=bool(int(is_synced)))
|
||
|
||
if role == 'master' and not filtered:
|
||
queryset = queryset.filter(status='work')
|
||
|
||
items = list(queryset.order_by('machine__name', 'date', 'task__deal__number', 'id'))
|
||
groups = {}
|
||
for item in items:
|
||
groups.setdefault(item.machine, []).append(item)
|
||
|
||
context['groups'] = list(groups.items())
|
||
context['printed_at'] = timezone.now()
|
||
context['end_date'] = end_date or ''
|
||
|
||
print_date_raw = end_date or start_date
|
||
print_date = None
|
||
if isinstance(print_date_raw, str) and print_date_raw:
|
||
try:
|
||
print_date = datetime.strptime(print_date_raw, '%Y-%m-%d').date()
|
||
except ValueError:
|
||
print_date = None
|
||
context['print_date'] = print_date
|
||
|
||
if start_date and end_date and start_date == end_date:
|
||
context['date_label'] = start_date
|
||
elif start_date and end_date:
|
||
context['date_label'] = f"{start_date} — {end_date}"
|
||
elif start_date:
|
||
context['date_label'] = f"c {start_date}"
|
||
elif end_date:
|
||
context['date_label'] = f"по {end_date}"
|
||
else:
|
||
context['date_label'] = ''
|
||
|
||
return context
|
||
|
||
|
||
# Вьюха детального вида и редактирования
|
||
class ItemUpdateView(LoginRequiredMixin, UpdateView):
|
||
model = Item
|
||
template_name = 'shiftflow/item_detail.html'
|
||
# Перечисляем поля, которые можно редактировать в сменке
|
||
fields = [
|
||
'machine', 'quantity_plan', 'quantity_fact',
|
||
'status', 'is_synced_1c',
|
||
'material_taken', 'usable_waste', 'scrap_weight'
|
||
]
|
||
context_object_name = 'item'
|
||
|
||
def get_context_data(self, **kwargs):
|
||
context = super().get_context_data(**kwargs)
|
||
# Обязательно добавляем роль в контекст этого шаблона!
|
||
if hasattr(self.request.user, 'profile'):
|
||
context['user_role'] = self.request.user.profile.role
|
||
return context
|
||
|
||
def post(self, request, *args, **kwargs):
|
||
self.object = self.get_object()
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else 'operator'
|
||
|
||
# Общие поля
|
||
self.object.material_taken = request.POST.get('material_taken', self.object.material_taken)
|
||
self.object.usable_waste = request.POST.get('usable_waste', self.object.usable_waste)
|
||
self.object.scrap_weight = request.POST.get('scrap_weight', self.object.scrap_weight or 0)
|
||
|
||
status = request.POST.get('status', self.object.status)
|
||
|
||
if role in ['operator', 'master']:
|
||
if status == 'done':
|
||
self.object.quantity_fact = self.object.quantity_plan
|
||
self.object.status = 'done'
|
||
self.object.save()
|
||
elif status == 'partial':
|
||
try:
|
||
fact = int(request.POST.get('quantity_fact', '0'))
|
||
except ValueError:
|
||
fact = 0
|
||
fact = max(0, min(fact, self.object.quantity_plan))
|
||
residual = self.object.quantity_plan - fact
|
||
self.object.quantity_fact = fact
|
||
self.object.status = 'partial'
|
||
self.object.save()
|
||
if residual > 0:
|
||
Item.objects.create(
|
||
task=self.object.task,
|
||
date=self.object.date,
|
||
machine=self.object.machine,
|
||
quantity_plan=residual,
|
||
quantity_fact=0,
|
||
status='leftover',
|
||
is_synced_1c=False,
|
||
)
|
||
else:
|
||
# Просто сохранить без спец-логики
|
||
return super().post(request, *args, **kwargs)
|
||
elif role == 'clerk':
|
||
# Учетчик может отмечать списание 1С
|
||
self.object.is_synced_1c = bool(request.POST.get('is_synced_1c'))
|
||
self.object.save()
|
||
else:
|
||
return super().post(request, *args, **kwargs)
|
||
|
||
return redirect('registry')
|
||
|
||
def get_success_url(self):
|
||
return reverse_lazy('registry') |