All checks were successful
Deploy MES Core / deploy (push) Successful in 10s
692 lines
28 KiB
Python
692 lines
28 KiB
Python
from datetime import datetime
|
||
|
||
from django.db.models import Case, ExpressionWrapper, F, IntegerField, Sum, Value, When
|
||
from django.db.models.functions import Coalesce
|
||
from django.http import JsonResponse
|
||
from django.shortcuts import get_object_or_404, redirect
|
||
from django.urls import reverse_lazy
|
||
from django.views import View
|
||
from django.views.generic import FormView, ListView, TemplateView, UpdateView
|
||
from django.contrib.auth.mixins import LoginRequiredMixin
|
||
from django.utils import timezone
|
||
|
||
from warehouse.models import Material, MaterialCategory, SteelGrade
|
||
|
||
from .forms import ProductionTaskCreateForm
|
||
from .models import Company, Deal, Item, Machine, ProductionTask
|
||
|
||
# Класс главной страницы (роутер)
|
||
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 PlanningView(LoginRequiredMixin, TemplateView):
|
||
template_name = 'shiftflow/planning.html'
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
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 ('admin' if self.request.user.is_superuser else 'operator')
|
||
context['user_role'] = role
|
||
|
||
status = (self.request.GET.get('status') or 'work').strip()
|
||
allowed = {k for k, _ in Deal.STATUS_CHOICES}
|
||
if status not in allowed:
|
||
status = 'work'
|
||
|
||
context['selected_status'] = status
|
||
context['deals'] = Deal.objects.select_related('company').filter(status=status).order_by('-id')
|
||
context['companies'] = Company.objects.all().order_by('name')
|
||
return context
|
||
|
||
|
||
class DealPlanningView(LoginRequiredMixin, TemplateView):
|
||
template_name = 'shiftflow/planning_deal.html'
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
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 ('admin' if self.request.user.is_superuser else 'operator')
|
||
context['user_role'] = role
|
||
|
||
deal = get_object_or_404(Deal.objects.select_related('company'), pk=self.kwargs['pk'])
|
||
context['deal'] = deal
|
||
|
||
tasks = ProductionTask.objects.filter(deal=deal).select_related('material').annotate(
|
||
done_qty=Coalesce(Sum('items__quantity_fact'), 0),
|
||
planned_qty=Coalesce(
|
||
Sum(
|
||
Case(
|
||
When(items__status__in=['work', 'leftover'], then=F('items__quantity_plan')),
|
||
default=Value(0),
|
||
output_field=IntegerField(),
|
||
)
|
||
),
|
||
0,
|
||
),
|
||
).annotate(
|
||
remaining_qty=ExpressionWrapper(
|
||
F('quantity_ordered') - F('done_qty') - F('planned_qty'),
|
||
output_field=IntegerField(),
|
||
)
|
||
).order_by('-id')
|
||
|
||
context['tasks'] = tasks
|
||
context['machines'] = Machine.objects.all()
|
||
return context
|
||
|
||
|
||
class PlanningAddView(LoginRequiredMixin, View):
|
||
def post(self, request, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
return redirect('planning')
|
||
|
||
task_id = request.POST.get('task_id')
|
||
machine_id = request.POST.get('machine_id')
|
||
qty_raw = request.POST.get('quantity_plan')
|
||
|
||
if not (task_id and task_id.isdigit() and machine_id and machine_id.isdigit() and qty_raw and qty_raw.isdigit()):
|
||
return redirect('planning')
|
||
|
||
qty = int(qty_raw)
|
||
if qty <= 0:
|
||
return redirect('planning')
|
||
|
||
Item.objects.create(
|
||
task_id=int(task_id),
|
||
machine_id=int(machine_id),
|
||
date=timezone.localdate(),
|
||
quantity_plan=qty,
|
||
quantity_fact=0,
|
||
status='work',
|
||
is_synced_1c=False,
|
||
)
|
||
|
||
next_url = request.POST.get('next') or ''
|
||
if next_url.startswith('/planning/deal/'):
|
||
return redirect(next_url)
|
||
|
||
return redirect('planning')
|
||
|
||
|
||
class ProductionTaskCreateView(LoginRequiredMixin, FormView):
|
||
template_name = 'shiftflow/task_create.html'
|
||
form_class = ProductionTaskCreateForm
|
||
success_url = reverse_lazy('planning')
|
||
|
||
def get_initial(self):
|
||
initial = super().get_initial()
|
||
deal_id = self.request.GET.get('deal')
|
||
if deal_id and str(deal_id).isdigit():
|
||
initial['deal'] = int(deal_id)
|
||
return initial
|
||
|
||
def dispatch(self, request, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
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 ('admin' if self.request.user.is_superuser else 'operator')
|
||
context['user_role'] = role
|
||
context['companies'] = Company.objects.all().order_by('name')
|
||
context['material_categories'] = MaterialCategory.objects.all().order_by('name')
|
||
context['steel_grades'] = SteelGrade.objects.all().order_by('name')
|
||
return context
|
||
|
||
def form_valid(self, form):
|
||
task = ProductionTask(
|
||
deal=form.cleaned_data['deal'],
|
||
drawing_name=form.cleaned_data.get('drawing_name') or 'Б/ч',
|
||
size_value=form.cleaned_data['size_value'],
|
||
material=form.cleaned_data['material'],
|
||
quantity_ordered=form.cleaned_data['quantity_ordered'],
|
||
is_bend=form.cleaned_data.get('is_bend') or False,
|
||
)
|
||
|
||
if form.cleaned_data.get('drawing_file'):
|
||
task.drawing_file = form.cleaned_data['drawing_file']
|
||
if form.cleaned_data.get('extra_drawing'):
|
||
task.extra_drawing = form.cleaned_data['extra_drawing']
|
||
|
||
task.save()
|
||
return super().form_valid(form)
|
||
|
||
|
||
class DealDetailView(LoginRequiredMixin, View):
|
||
def get(self, request, pk, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
return JsonResponse({'error': 'forbidden'}, status=403)
|
||
|
||
deal = get_object_or_404(Deal, pk=pk)
|
||
return JsonResponse({
|
||
'id': deal.id,
|
||
'number': deal.number,
|
||
'status': deal.status,
|
||
'company_id': deal.company_id,
|
||
'description': deal.description or '',
|
||
})
|
||
|
||
|
||
class DealUpsertView(LoginRequiredMixin, View):
|
||
def post(self, request, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
return JsonResponse({'error': 'forbidden'}, status=403)
|
||
|
||
deal_id = request.POST.get('id')
|
||
number = (request.POST.get('number') or '').strip()
|
||
description = (request.POST.get('description') or '').strip()
|
||
company_id = request.POST.get('company_id')
|
||
status = (request.POST.get('status') or 'work').strip()
|
||
|
||
if not number:
|
||
return JsonResponse({'error': 'number_required'}, status=400)
|
||
|
||
if deal_id and str(deal_id).isdigit():
|
||
deal = get_object_or_404(Deal, pk=int(deal_id))
|
||
deal.number = number
|
||
else:
|
||
deal, _ = Deal.objects.get_or_create(number=number)
|
||
|
||
allowed = {k for k, _ in Deal.STATUS_CHOICES}
|
||
if status not in allowed:
|
||
status = 'work'
|
||
|
||
deal.status = status
|
||
deal.description = description
|
||
if company_id and str(company_id).isdigit():
|
||
deal.company_id = int(company_id)
|
||
else:
|
||
deal.company_id = None
|
||
|
||
deal.save()
|
||
return JsonResponse({'id': deal.id, 'label': deal.number})
|
||
|
||
|
||
class MaterialDetailView(LoginRequiredMixin, View):
|
||
def get(self, request, pk, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
return JsonResponse({'error': 'forbidden'}, status=403)
|
||
|
||
material = get_object_or_404(Material, pk=pk)
|
||
return JsonResponse({
|
||
'id': material.id,
|
||
'category_id': material.category_id,
|
||
'steel_grade_id': material.steel_grade_id,
|
||
'name': material.name,
|
||
'full_name': material.full_name,
|
||
})
|
||
|
||
|
||
class MaterialUpsertView(LoginRequiredMixin, View):
|
||
def post(self, request, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
return JsonResponse({'error': 'forbidden'}, status=403)
|
||
|
||
material_id = request.POST.get('id')
|
||
category_id = request.POST.get('category_id')
|
||
steel_grade_id = request.POST.get('steel_grade_id')
|
||
name = (request.POST.get('name') or '').strip()
|
||
|
||
if not (category_id and str(category_id).isdigit() and name):
|
||
return JsonResponse({'error': 'invalid'}, status=400)
|
||
|
||
if material_id and str(material_id).isdigit():
|
||
material = get_object_or_404(Material, pk=int(material_id))
|
||
else:
|
||
material = Material()
|
||
|
||
material.category_id = int(category_id)
|
||
material.name = name
|
||
if steel_grade_id and str(steel_grade_id).isdigit():
|
||
material.steel_grade_id = int(steel_grade_id)
|
||
else:
|
||
material.steel_grade_id = None
|
||
|
||
material.save()
|
||
return JsonResponse({'id': material.id, 'label': material.full_name})
|
||
|
||
|
||
class CompanyUpsertView(LoginRequiredMixin, View):
|
||
def post(self, request, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
return JsonResponse({'error': 'forbidden'}, status=403)
|
||
|
||
company_id = request.POST.get('id')
|
||
name = (request.POST.get('name') or '').strip()
|
||
description = (request.POST.get('description') or '').strip()
|
||
|
||
if not name:
|
||
return JsonResponse({'error': 'name_required'}, status=400)
|
||
|
||
if company_id and str(company_id).isdigit():
|
||
company = get_object_or_404(Company, pk=int(company_id))
|
||
company.name = name
|
||
else:
|
||
company, _ = Company.objects.get_or_create(name=name)
|
||
|
||
company.description = description
|
||
company.save()
|
||
return JsonResponse({'id': company.id, 'label': company.name})
|
||
|
||
|
||
class MaterialCategoryUpsertView(LoginRequiredMixin, View):
|
||
def post(self, request, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
return JsonResponse({'error': 'forbidden'}, status=403)
|
||
|
||
category_id = request.POST.get('id')
|
||
name = (request.POST.get('name') or '').strip()
|
||
gost_standard = (request.POST.get('gost_standard') or '').strip()
|
||
|
||
if not name:
|
||
return JsonResponse({'error': 'name_required'}, status=400)
|
||
|
||
if category_id and str(category_id).isdigit():
|
||
category = get_object_or_404(MaterialCategory, pk=int(category_id))
|
||
category.name = name
|
||
else:
|
||
category, _ = MaterialCategory.objects.get_or_create(name=name)
|
||
|
||
category.gost_standard = gost_standard
|
||
category.save()
|
||
return JsonResponse({'id': category.id, 'label': category.name})
|
||
|
||
|
||
class SteelGradeUpsertView(LoginRequiredMixin, View):
|
||
def post(self, request, *args, **kwargs):
|
||
profile = getattr(request.user, 'profile', None)
|
||
role = profile.role if profile else ('admin' if request.user.is_superuser else 'operator')
|
||
if role not in ['admin', 'technologist']:
|
||
return JsonResponse({'error': 'forbidden'}, status=403)
|
||
|
||
grade_id = request.POST.get('id')
|
||
name = (request.POST.get('name') or '').strip()
|
||
gost_standard = (request.POST.get('gost_standard') or '').strip()
|
||
|
||
if not name:
|
||
return JsonResponse({'error': 'name_required'}, status=400)
|
||
|
||
if grade_id and str(grade_id).isdigit():
|
||
grade = get_object_or_404(SteelGrade, pk=int(grade_id))
|
||
grade.name = name
|
||
else:
|
||
grade, _ = SteelGrade.objects.get_or_create(name=name)
|
||
|
||
grade.gost_standard = gost_standard
|
||
grade.save()
|
||
return JsonResponse({'id': grade.id, 'label': grade.name})
|
||
|
||
|
||
# Вьюха детального вида и редактирования
|
||
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') |