@@ -74,10 +82,27 @@
{% endif %}
{% endif %}
- {% if user_role == 'admin' %}
+ {% if user_role in 'admin,technologist' %}
{% endif %}
- {% if user_role in 'admin,clerk' %}
- {% if item.status == 'done' or item.quantity_fact > 0 %}
+ {% if user_role == 'clerk' %}
+
+
+ {% if item.status == 'done' or item.status == 'partial' %}
{% endif %}
- {% if user_role == 'clerk' %}
diff --git a/shiftflow/templates/shiftflow/partials/_filter.html b/shiftflow/templates/shiftflow/partials/_filter.html
index e31caf0..471a8b0 100644
--- a/shiftflow/templates/shiftflow/partials/_filter.html
+++ b/shiftflow/templates/shiftflow/partials/_filter.html
@@ -22,8 +22,11 @@
Статус:
{% if user_role == 'operator' %}
-
- В работе
+
+
+
+
+
{% else %}
@@ -72,7 +75,10 @@
document.addEventListener('DOMContentLoaded', function(){
const s = document.querySelector('input[name="start_date"]');
const e = document.querySelector('input[name="end_date"]');
- const today = new Date().toISOString().slice(0,10);
+ const now = new Date();
+ const mm = String(now.getMonth() + 1).padStart(2, '0');
+ const dd = String(now.getDate()).padStart(2, '0');
+ const today = `${now.getFullYear()}-${mm}-${dd}`;
if (s && !s.value) s.value = today;
if (e && !e.value) e.value = today;
});
diff --git a/shiftflow/templates/shiftflow/registry.html b/shiftflow/templates/shiftflow/registry.html
index f50f69b..8e13a45 100644
--- a/shiftflow/templates/shiftflow/registry.html
+++ b/shiftflow/templates/shiftflow/registry.html
@@ -63,7 +63,7 @@
{% endif %}
-
+
{{ item.get_status_display }}
|
diff --git a/shiftflow/views.py b/shiftflow/views.py
index 8591b0b..908fbf8 100644
--- a/shiftflow/views.py
+++ b/shiftflow/views.py
@@ -55,8 +55,13 @@ class RegistryView(LoginRequiredMixin, ListView):
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'])
+ 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)
@@ -71,7 +76,9 @@ class RegistryView(LoginRequiredMixin, ListView):
# Ограничения по ролям
if role == 'operator':
user_machines = profile.machines.all() if profile else Machine.objects.none()
- queryset = queryset.filter(machine__in=user_machines, status='work')
+ 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')
@@ -89,10 +96,15 @@ class RegistryView(LoginRequiredMixin, ListView):
filtered = self.request.GET.get('filtered')
if not filtered:
- today_str = timezone.now().date().strftime('%Y-%m-%d')
+ today_str = timezone.localdate().strftime('%Y-%m-%d')
context['start_date'] = today_str
context['end_date'] = today_str
- context['selected_statuses'] = ['work', 'leftover']
+ 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:
@@ -147,7 +159,7 @@ class RegistryPrintView(LoginRequiredMixin, TemplateView):
start_date = self.request.GET.get('start_date')
end_date = self.request.GET.get('end_date')
if not filtered:
- today = timezone.now().date()
+ today = timezone.localdate()
queryset = queryset.filter(date=today, status__in=['work', 'leftover'])
start_date = today.strftime('%Y-%m-%d')
end_date = start_date
@@ -210,38 +222,110 @@ class ItemUpdateView(LoginRequiredMixin, UpdateView):
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
+ 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 'operator'
+ role = profile.role if profile else ('admin' if request.user.is_superuser 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)
+ 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)
- status = request.POST.get('status', self.object.status)
+ 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()
- elif status == 'partial':
+ 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,
@@ -252,15 +336,17 @@ class ItemUpdateView(LoginRequiredMixin, UpdateView):
status='leftover',
is_synced_1c=False,
)
- else:
- # Просто сохранить без спец-логики
- return super().post(request, *args, **kwargs)
- elif role == 'clerk':
- # Учетчик может отмечать списание 1С
+
+ 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()
- else:
- return super().post(request, *args, **kwargs)
+ self.object.save(update_fields=['is_synced_1c'])
+ return redirect('registry')
return redirect('registry')
diff --git a/warehouse/migrations/0003_alter_material_full_name.py b/warehouse/migrations/0003_alter_material_full_name.py
new file mode 100644
index 0000000..8d7adea
--- /dev/null
+++ b/warehouse/migrations/0003_alter_material_full_name.py
@@ -0,0 +1,18 @@
+# Generated by Django 6.0.3 on 2026-03-29 21:15
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('warehouse', '0002_materialcategory_gost_standard'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='material',
+ name='full_name',
+ field=models.CharField(blank=True, help_text='Генерируется автоматически', max_length=500, verbose_name='Полное наименование'),
+ ),
+ ]