From ff0b791a24724a4f67df2d2b1e27b66afa2a6960 Mon Sep 17 00:00:00 2001 From: ackFromRedmi Date: Sun, 29 Mar 2026 23:19:13 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=D0=BB=D0=B8=20=D0=B3=D0=B5=D0=BD=D0=B5=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8E=20=D1=81=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shiftflow/admin.py | 7 +- .../migrations/0007_machine_machine_type.py | 18 +++ shiftflow/models.py | 11 +- .../templates/shiftflow/partials/_filter.html | 12 +- shiftflow/templates/shiftflow/registry.html | 7 +- .../templates/shiftflow/registry_print.html | 148 ++++++++++++++++++ shiftflow/urls.py | 9 +- shiftflow/views.py | 95 ++++++++++- static/css/style.css | 8 + warehouse/models.py | 14 +- 10 files changed, 311 insertions(+), 18 deletions(-) create mode 100644 shiftflow/migrations/0007_machine_machine_type.py create mode 100644 shiftflow/templates/shiftflow/registry_print.html diff --git a/shiftflow/admin.py b/shiftflow/admin.py index d78645a..0199fcb 100644 --- a/shiftflow/admin.py +++ b/shiftflow/admin.py @@ -53,8 +53,11 @@ class ItemAdmin(admin.ModelAdmin): return obj.task.drawing_name if obj.task else "-" get_drawing.short_description = 'Деталь' -# Регистрация станков просто списком -admin.site.register(Machine) +@admin.register(Machine) +class MachineAdmin(admin.ModelAdmin): + list_display = ('name', 'machine_type') + list_filter = ('machine_type',) + search_fields = ('name',) @admin.register(EmployeeProfile) class EmployeeProfileAdmin(admin.ModelAdmin): diff --git a/shiftflow/migrations/0007_machine_machine_type.py b/shiftflow/migrations/0007_machine_machine_type.py new file mode 100644 index 0000000..fda14c3 --- /dev/null +++ b/shiftflow/migrations/0007_machine_machine_type.py @@ -0,0 +1,18 @@ +# Generated by Django 6.0.3 on 2026-03-29 19:24 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shiftflow', '0006_alter_item_options_alter_item_status'), + ] + + operations = [ + migrations.AddField( + model_name='machine', + name='machine_type', + field=models.CharField(choices=[('linear', 'Линейный'), ('sheet', 'Листовой')], default='linear', max_length=10, verbose_name='Тип станка'), + ), + ] diff --git a/shiftflow/models.py b/shiftflow/models.py index 9971bb4..2eabeeb 100644 --- a/shiftflow/models.py +++ b/shiftflow/models.py @@ -20,9 +20,18 @@ class Machine(models.Model): Список производственных участков (станков). Используется для фильтрации сменных заданий для конкретных операторов. """ + + MACHINE_TYPE_CHOICES = [ + ('linear', 'Линейный'), + ('sheet', 'Листовой'), + ] + name = models.CharField("Название станка", max_length=100) + machine_type = models.CharField("Тип станка", max_length=10, choices=MACHINE_TYPE_CHOICES, default='linear') - def __str__(self): return self.name + def __str__(self): + return self.name + class Meta: verbose_name = "Станок"; verbose_name_plural = "Станки" diff --git a/shiftflow/templates/shiftflow/partials/_filter.html b/shiftflow/templates/shiftflow/partials/_filter.html index 1473704..e31caf0 100644 --- a/shiftflow/templates/shiftflow/partials/_filter.html +++ b/shiftflow/templates/shiftflow/partials/_filter.html @@ -43,9 +43,9 @@ {% if user_role in 'admin,technologist,clerk' %} -
+
- @@ -53,13 +53,13 @@
{% endif %} -
+
- +
-
+
- +
diff --git a/shiftflow/templates/shiftflow/registry.html b/shiftflow/templates/shiftflow/registry.html index 41e0371..f50f69b 100644 --- a/shiftflow/templates/shiftflow/registry.html +++ b/shiftflow/templates/shiftflow/registry.html @@ -4,8 +4,13 @@ {% include 'shiftflow/partials/_filter.html' %}
-
+

Реестр заданий

+ {% if user_role in 'admin,technologist,master' %} + + Печать + + {% endif %}
diff --git a/shiftflow/templates/shiftflow/registry_print.html b/shiftflow/templates/shiftflow/registry_print.html new file mode 100644 index 0000000..29c0ed2 --- /dev/null +++ b/shiftflow/templates/shiftflow/registry_print.html @@ -0,0 +1,148 @@ +{% load static %} + + + + + + Сменное задание + + + + + +
+ + + Назад + +
+ {{ printed_at|date:"d.m.Y H:i" }} +
+
+ + {% for machine, items in groups %} +
+ + + {% if machine.machine_type == 'sheet' %} + + + + + + + + + + + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + + + + + + {% endfor %} + + + {% else %} + + + + + + + + + + + + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + + + + + + + {% endfor %} + + + {% endif %} + +
+
+
Оператор
+
+
+
Выдал
+
+
+
+ {% endfor %} + + \ No newline at end of file diff --git a/shiftflow/urls.py b/shiftflow/urls.py index c215e27..4c4e4c1 100644 --- a/shiftflow/urls.py +++ b/shiftflow/urls.py @@ -1,5 +1,10 @@ from django.urls import path -from .views import IndexView, ItemUpdateView, RegistryView +from .views import ( + IndexView, + ItemUpdateView, + RegistryView, + RegistryPrintView, +) urlpatterns = [ # Главная страница (путь пустой) @@ -7,5 +12,7 @@ urlpatterns = [ # Реестр path('registry/', RegistryView.as_view(), name='registry'), + # Печать сменного листа + path('registry/print/', RegistryPrintView.as_view(), name='registry_print'), path('item//', ItemUpdateView.as_view(), name='item_detail'), ] \ No newline at end of file diff --git a/shiftflow/views.py b/shiftflow/views.py index b093589..8591b0b 100644 --- a/shiftflow/views.py +++ b/shiftflow/views.py @@ -1,3 +1,5 @@ +from datetime import datetime + from django.shortcuts import redirect from django.urls import reverse_lazy from django.views.generic import TemplateView, ListView, UpdateView @@ -102,7 +104,98 @@ class RegistryView(LoginRequiredMixin, ListView): 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.now().date() + 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 diff --git a/static/css/style.css b/static/css/style.css index 3b35a3a..541764d 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -117,6 +117,14 @@ body { color: #ffffff !important; } +.registry-filter-date { + width: 130px; +} + +.registry-filter-1c { + width: 120px; +} + /* Специальный класс для центрирования окна логина (вернем его только там) */ .flex-center-center { display: flex; diff --git a/warehouse/models.py b/warehouse/models.py index c338be5..b72bf8f 100644 --- a/warehouse/models.py +++ b/warehouse/models.py @@ -30,17 +30,19 @@ class Material(models.Model): category = models.ForeignKey(MaterialCategory, on_delete=models.PROTECT, verbose_name="Категория") steel_grade = models.ForeignKey(SteelGrade, on_delete=models.PROTECT, verbose_name="Марка стали", null=True, blank=True) name = models.CharField("Наименование (размер/характеристики)", max_length=255) - full_name = models.CharField("Полное наименование", max_length=500, blank=True, help_text="Генерируется автоматически, если пусто") - + full_name = models.CharField("Полное наименование", max_length=500, blank=True, help_text="Генерируется автоматически") + class Meta: verbose_name = "Материал (номенклатура)" verbose_name_plural = "Материалы" def save(self, *args, **kwargs): - if not self.full_name: - grade_str = f" {self.steel_grade.name}" if self.steel_grade else "" - self.full_name = f"{self.category.name} {self.name}{grade_str}" + category_part = (self.category.name or '').strip() if self.category_id else '' + name_part = (self.name or '').strip() + grade_part = (self.steel_grade.name or '').strip() if self.steel_grade_id else '' + + self.full_name = ' '.join([p for p in [category_part, name_part, grade_part] if p]) super().save(*args, **kwargs) def __str__(self): - return self.full_name or f"{self.category.name} {self.name}" + return self.full_name or ' '.join([p for p in [(self.category.name if self.category_id else ''), self.name, (self.steel_grade.name if self.steel_grade_id else '')] if p]).strip()