Files
MES_Core/shiftflow/views.py
2026-04-02 08:21:09 +03:00

812 lines
33 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.

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_qs = 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')
tasks = list(tasks_qs)
for t in tasks:
need = int(t.quantity_ordered or 0)
done_qty = int(t.done_qty or 0)
planned_qty = int(t.planned_qty or 0)
if need > 0:
done_pct = int(round(done_qty * 100 / need))
plan_pct = int(round(planned_qty * 100 / need))
else:
done_pct = 0
plan_pct = 0
done_width = max(0, min(100, done_pct))
plan_width = max(0, min(100 - done_width, plan_pct))
t.done_pct = done_pct
t.plan_pct = plan_pct
t.done_width = done_width
t.plan_width = plan_width
context['tasks'] = tasks
context['machines'] = Machine.objects.all()
return context
class TaskItemsView(LoginRequiredMixin, TemplateView):
template_name = 'shiftflow/task_items.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
task = get_object_or_404(
ProductionTask.objects.select_related('deal', 'deal__company', 'material'),
pk=self.kwargs['pk'],
)
context['task'] = task
items = Item.objects.filter(task=task).select_related('machine').order_by('-date', 'machine__name', '-id')
context['items'] = items
return context
class CustomersView(LoginRequiredMixin, TemplateView):
template_name = 'shiftflow/customers.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
companies = Company.objects.all().order_by('name')
context['companies'] = companies
return context
class CustomerDealsView(LoginRequiredMixin, TemplateView):
template_name = 'shiftflow/customer_deals.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
company = get_object_or_404(Company, pk=self.kwargs['pk'])
context['company'] = company
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(company=company, status=status).order_by('-id')
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()
next_url = (self.request.POST.get('next') or '').strip()
if next_url.startswith('/'):
return redirect(next_url)
return redirect('planning_deal', pk=task.deal_id)
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']:
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()
status = request.POST.get('status', self.object.status)
machine_changed = False
if role == 'master':
machine_id = request.POST.get('machine')
if machine_id and machine_id.isdigit():
self.object.machine_id = int(machine_id)
machine_changed = True
# Разрешаем мастеру редактировать операторские поля всегда,
# оператору — только в процессе закрытия
if role == 'operator' and self.object.status != 'work':
return redirect('registry')
errors = []
if role == 'operator' and self.object.status == 'work':
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('Поле "Лом (кг)" должно быть числом')
if errors:
context = self.get_context_data()
context['errors'] = errors
return self.render_to_response(context)
if material_taken:
self.object.material_taken = material_taken
if usable_waste:
self.object.usable_waste = usable_waste
if scrap_weight is not None:
self.object.scrap_weight = scrap_weight
# Логика закрытия доступна и мастеру, и оператору, но только из 'work'
if self.object.status == 'work' and status == 'done':
self.object.quantity_fact = self.object.quantity_plan
self.object.status = 'done'
self.object.save()
return redirect('registry')
if self.object.status == 'work' and 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')
# Если статус не менялся (или не 'work'), просто сохраняем поля
update_fields = ['material_taken', 'usable_waste', 'scrap_weight']
if machine_changed:
update_fields.append('machine')
self.object.save(update_fields=update_fields)
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')