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.localdate() if role == 'clerk': queryset = queryset.filter(date=today, status__in=['done', 'partial']) elif role in ['operator', 'master']: queryset = queryset.filter(date=today, status__in=['work']) else: 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) if not filtered: queryset = queryset.filter(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.localdate().strftime('%Y-%m-%d') context['start_date'] = today_str context['end_date'] = today_str if role == 'clerk': context['selected_statuses'] = ['closed'] elif role in ['operator', 'master']: context['selected_statuses'] = ['work'] else: 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.localdate() 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) profile = getattr(self.request.user, 'profile', None) role = profile.role if profile else ('admin' if self.request.user.is_superuser else 'operator') context['user_role'] = role context['machines'] = Machine.objects.all() 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 ('admin' if request.user.is_superuser else 'operator') if role in ['admin', 'technologist']: machine_id = request.POST.get('machine') if machine_id and machine_id.isdigit(): self.object.machine_id = int(machine_id) date_value = request.POST.get('date') if date_value: self.object.date = date_value quantity_plan = request.POST.get('quantity_plan') if quantity_plan and quantity_plan.isdigit(): self.object.quantity_plan = int(quantity_plan) quantity_fact = request.POST.get('quantity_fact') if quantity_fact and quantity_fact.isdigit(): self.object.quantity_fact = int(quantity_fact) status = request.POST.get('status') allowed_statuses = {k for k, _ in self.object.STATUS_CHOICES} if status in allowed_statuses: self.object.status = status self.object.is_synced_1c = bool(request.POST.get('is_synced_1c')) 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) scrap_weight = request.POST.get('scrap_weight') if scrap_weight is not None and scrap_weight != '': try: self.object.scrap_weight = float(scrap_weight) except ValueError: pass self.object.save() return redirect('registry') if role in ['operator', 'master']: if self.object.status != 'work': return redirect('registry') material_taken = (request.POST.get('material_taken') or '').strip() usable_waste = (request.POST.get('usable_waste') or '').strip() scrap_weight_raw = (request.POST.get('scrap_weight') or '').strip() errors = [] if not material_taken: errors.append('Заполни поле "Взятый материал"') if not usable_waste: errors.append('Заполни поле "Остаток ДО"') if scrap_weight_raw == '': errors.append('Заполни поле "Лом (кг)" (можно 0)') scrap_weight = None if scrap_weight_raw != '': try: scrap_weight = float(scrap_weight_raw) except ValueError: errors.append('Поле "Лом (кг)" должно быть числом') status = request.POST.get('status', self.object.status) if errors: context = self.get_context_data() context['errors'] = errors return self.render_to_response(context) self.object.material_taken = material_taken self.object.usable_waste = usable_waste if scrap_weight is not None: self.object.scrap_weight = scrap_weight if status == 'done': self.object.quantity_fact = self.object.quantity_plan self.object.status = 'done' self.object.save() return redirect('registry') if status == 'partial': try: fact = int(request.POST.get('quantity_fact', '0')) except ValueError: fact = 0 if fact <= 0: context = self.get_context_data() context['errors'] = ['При частичном закрытии укажи, сколько сделано (больше 0)'] return self.render_to_response(context) 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, ) return redirect('registry') return redirect('registry') if role == 'clerk': if self.object.status not in ['done', 'partial']: return redirect('registry') self.object.is_synced_1c = bool(request.POST.get('is_synced_1c')) self.object.save(update_fields=['is_synced_1c']) return redirect('registry') return redirect('registry') def get_success_url(self): return reverse_lazy('registry')