diff --git a/requirements.txtсЫВпывмЫВ b/requirements.txtсЫВпывмЫВ
new file mode 100644
index 0000000..7301fc7
Binary files /dev/null and b/requirements.txtсЫВпывмЫВ differ
diff --git a/shiftflow/templates/shiftflow/warehouse_stocks.html b/shiftflow/templates/shiftflow/warehouse_stocks.html
index e30801e..c72b75b 100644
--- a/shiftflow/templates/shiftflow/warehouse_stocks.html
+++ b/shiftflow/templates/shiftflow/warehouse_stocks.html
@@ -224,7 +224,7 @@
diff --git a/shiftflow/views.py b/shiftflow/views.py
index 23dd54f..0850c2b 100644
--- a/shiftflow/views.py
+++ b/shiftflow/views.py
@@ -966,9 +966,8 @@ class PlanningStagesView(LoginRequiredMixin, TemplateView):
template_name = 'shiftflow/planning_stages.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', 'master', 'clerk', 'manager', 'observer']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'manager', 'observer', 'prod_head', 'director']):
return redirect('registry')
return super().dispatch(request, *args, **kwargs)
@@ -1888,17 +1887,18 @@ 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', 'master', 'clerk', 'prod_head', 'director']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'prod_head', 'director', 'observer']):
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')
+ roles = get_user_roles(self.request.user)
+ role = primary_role(roles)
context['user_role'] = role
+ context['user_roles'] = sorted(roles)
allowed_ws = list(profile.allowed_workshops.values_list('id', flat=True)) if profile else []
context['allowed_workshop_ids'] = allowed_ws
@@ -1923,17 +1923,18 @@ 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', 'master', 'clerk']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'prod_head', 'director', 'observer']):
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')
+ roles = get_user_roles(self.request.user)
+ role = primary_role(roles)
context['user_role'] = role
+ context['user_roles'] = sorted(roles)
allowed_ws = list(profile.allowed_workshops.values_list('id', flat=True)) if profile else []
context['allowed_workshop_ids'] = allowed_ws
@@ -2525,17 +2526,17 @@ 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', 'master', 'clerk']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'prod_head', 'director', 'observer']):
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')
+ roles = get_user_roles(self.request.user)
+ role = primary_role(roles)
context['user_role'] = role
+ context['user_roles'] = sorted(roles)
companies = Company.objects.all().order_by('name')
context['companies'] = companies
@@ -2546,17 +2547,18 @@ 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', 'master', 'clerk']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'prod_head', 'director', 'observer']):
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')
+ roles = get_user_roles(self.request.user)
+ role = primary_role(roles)
context['user_role'] = role
+ context['user_roles'] = sorted(roles)
allowed_ws = list(profile.allowed_workshops.values_list('id', flat=True)) if profile else []
context['allowed_workshop_ids'] = allowed_ws
@@ -3296,17 +3298,17 @@ class DirectoriesView(LoginRequiredMixin, TemplateView):
template_name = 'shiftflow/directories.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', 'master', 'clerk', 'prod_head', 'director']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'observer', 'prod_head', 'director']):
return redirect('registry')
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
ctx = 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')
+ roles = get_user_roles(self.request.user)
+ role = primary_role(roles)
ctx['user_role'] = role
+ ctx['user_roles'] = sorted(roles)
return ctx
@@ -3314,26 +3316,25 @@ class LocationsCatalogView(LoginRequiredMixin, TemplateView):
template_name = 'shiftflow/locations_catalog.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', 'master', 'clerk', 'prod_head', 'director']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'observer', 'prod_head', 'director']):
return redirect('registry')
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
ctx = 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')
+ roles = get_user_roles(self.request.user)
+ role = primary_role(roles)
ctx['user_role'] = role
- ctx['can_edit'] = role in ['admin', 'prod_head', 'director']
+ ctx['user_roles'] = sorted(roles)
+ ctx['can_edit'] = has_any_role(roles, ['admin', 'prod_head', 'director'])
ctx['locations'] = list(Location.objects.order_by('name'))
return ctx
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', 'prod_head', 'director']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'prod_head', 'director']):
return redirect('locations_catalog')
action = (request.POST.get('action') or '').strip()
@@ -3377,17 +3378,17 @@ class WorkshopsCatalogView(LoginRequiredMixin, TemplateView):
template_name = 'shiftflow/workshops_catalog.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', 'master', 'clerk', 'prod_head', 'director']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'observer', 'prod_head', 'director']):
return redirect('registry')
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
ctx = 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')
+ roles = get_user_roles(self.request.user)
+ role = primary_role(roles)
ctx['user_role'] = role
+ ctx['user_roles'] = sorted(roles)
workshops = list(Workshop.objects.select_related('location').order_by('id'))
ws_ids = [int(w.id) for w in workshops]
@@ -3414,12 +3415,12 @@ class MachinesCatalogView(LoginRequiredMixin, TemplateView):
template_name = 'shiftflow/machines_catalog.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', 'master', 'clerk', 'prod_head', 'director']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'observer', 'prod_head', 'director']):
return redirect('registry')
- self.role = role
- self.can_edit = role in ['admin', 'prod_head', 'director']
+ self.roles = roles
+ self.role = primary_role(roles)
+ self.can_edit = has_any_role(roles, ['admin', 'prod_head', 'director'])
return super().dispatch(request, *args, **kwargs)
def _workshop_id(self):
@@ -3889,17 +3890,18 @@ class WarehouseStocksView(LoginRequiredMixin, TemplateView):
template_name = 'shiftflow/warehouse_stocks.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', 'master', 'clerk', 'observer']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'observer', 'prod_head', 'director']):
return redirect('registry')
return super().dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
ctx = 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')
+ roles = get_user_roles(self.request.user)
+ role = primary_role(roles)
ctx['user_role'] = role
+ ctx['user_roles'] = sorted(roles)
ship_loc = (
Location.objects.filter(
@@ -3975,8 +3977,20 @@ class WarehouseStocksView(LoginRequiredMixin, TemplateView):
ctx['selected_kind'] = kind
ctx['q'] = q
- ctx['can_transfer'] = role in ['admin', 'technologist', 'master', 'clerk']
- ctx['can_receive'] = role in ['admin', 'technologist', 'master', 'clerk']
+ ctx['can_transfer'] = has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'prod_head', 'director'])
+ ctx['can_receive'] = has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'prod_head', 'director'])
+
+ allowed_transfer_locations = None
+ if role == 'master' and not has_any_role(roles, ['admin', 'technologist', 'clerk', 'prod_head', 'director']):
+ allowed_ws_ids = list(profile.allowed_workshops.values_list('id', flat=True)) if profile else []
+ if not allowed_ws_ids and profile:
+ user_machine_ids = list(profile.machines.values_list('id', flat=True))
+ allowed_ws_ids = list(Machine.objects.filter(id__in=user_machine_ids).exclude(workshop_id__isnull=True).values_list('workshop_id', flat=True))
+ allowed_loc_ids = list(Workshop.objects.filter(id__in=allowed_ws_ids).exclude(location_id__isnull=True).values_list('location_id', flat=True))
+ if allowed_loc_ids:
+ allowed_transfer_locations = list(Location.objects.filter(id__in=allowed_loc_ids).order_by('name'))
+
+ ctx['transfer_locations'] = allowed_transfer_locations if allowed_transfer_locations is not None else locations
ctx['materials'] = Material.objects.select_related('category').all().order_by('full_name')
ctx['entities'] = ProductEntity.objects.all().order_by('drawing_number', 'name')
@@ -3991,8 +4005,9 @@ class WarehouseStocksView(LoginRequiredMixin, TemplateView):
class WarehouseTransferCreateView(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', 'master', 'clerk']:
+ roles = get_user_roles(request.user)
+ role = primary_role(roles)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'prod_head', 'director']):
return JsonResponse({'error': 'forbidden'}, status=403)
stock_item_id = (request.POST.get('stock_item_id') or '').strip()
@@ -4021,6 +4036,17 @@ class WarehouseTransferCreateView(LoginRequiredMixin, View):
messages.error(request, 'Склад назначения должен отличаться от склада-источника.')
return redirect(next_url)
+ if role == 'master' and not has_any_role(roles, ['admin', 'technologist', 'clerk', 'prod_head', 'director']):
+ allowed_ws_ids = list(profile.allowed_workshops.values_list('id', flat=True)) if profile else []
+ if not allowed_ws_ids and profile:
+ user_machine_ids = list(profile.machines.values_list('id', flat=True))
+ allowed_ws_ids = list(Machine.objects.filter(id__in=user_machine_ids).exclude(workshop_id__isnull=True).values_list('workshop_id', flat=True))
+
+ allowed_loc_ids = list(Workshop.objects.filter(id__in=allowed_ws_ids).exclude(location_id__isnull=True).values_list('location_id', flat=True))
+ if not allowed_loc_ids or int(to_location_id) not in {int(x) for x in allowed_loc_ids}:
+ messages.error(request, 'Мастер может перемещать только на склад своего цеха.')
+ return redirect(next_url)
+
tr = TransferRecord.objects.create(
from_location_id=si.location_id,
to_location_id=int(to_location_id),
@@ -4044,9 +4070,8 @@ class WarehouseTransferCreateView(LoginRequiredMixin, View):
class WarehouseReceiptCreateView(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', 'master', 'clerk']:
+ roles = get_user_roles(request.user)
+ if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'prod_head', 'director']):
return JsonResponse({'error': 'forbidden'}, status=403)
next_url = (request.POST.get('next') or '').strip()