From 27d6f75dbece7de8099f0a51b1182e3da6be135b Mon Sep 17 00:00:00 2001 From: ackFromRedmi Date: Thu, 16 Apr 2026 08:28:15 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D1=80=D0=B5=D0=B5=D1=81=D1=82=D1=80=20=D1=81?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D0=B9,=20=D0=BF=D1=80=D0=B0=D0=B2=D0=B0=20=D0=BE?= =?UTF-8?q?=D0=BF=D1=80=D0=B5=D1=80=D0=B0=D1=82=D0=BE=D1=80=D0=B0,=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D0=BB=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=B4=D0=BB=D1=8F=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D1=81=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE?= =?UTF-8?q?=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD=D0=BE=20(=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=82=D0=BE=D1=80=D0=B0?= =?UTF-8?q?)=20=D0=BF=D0=BE=D0=B4=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20ui?= =?UTF-8?q?=20=D0=B2=20=D0=BE=D0=BA=D0=BD=D0=B5=20=D0=BA=D0=B0=D1=80=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BA=D0=B8=20=D1=81=D0=BC=D0=B5=D0=BD=D0=BD=D0=BE?= =?UTF-8?q?=D0=B3=D0=BE=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...034_workitem_quantity_reported_and_more.py | 23 ++++++++ shiftflow/models.py | 1 + .../shiftflow/partials/_workitems_table.html | 10 +++- .../templates/shiftflow/workitem_detail.html | 56 +++++++++++++------ shiftflow/views.py | 29 +++++----- 5 files changed, 87 insertions(+), 32 deletions(-) create mode 100644 shiftflow/migrations/0034_workitem_quantity_reported_and_more.py diff --git a/shiftflow/migrations/0034_workitem_quantity_reported_and_more.py b/shiftflow/migrations/0034_workitem_quantity_reported_and_more.py new file mode 100644 index 0000000..0c8a14d --- /dev/null +++ b/shiftflow/migrations/0034_workitem_quantity_reported_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 6.0.3 on 2026-04-16 05:08 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('shiftflow', '0033_cuttingsession_is_synced_1c_and_more'), + ] + + operations = [ + migrations.AddField( + model_name='workitem', + name='quantity_reported', + field=models.PositiveIntegerField(default=0, verbose_name='Прогресс (оператор), шт'), + ), + migrations.AlterField( + model_name='machine', + name='machine_type', + field=models.CharField(choices=[('linear', 'Линейный'), ('sheet', 'Листовой'), ('post', 'Пост')], default='linear', max_length=10, verbose_name='Тип станка'), + ), + ] diff --git a/shiftflow/models.py b/shiftflow/models.py index 5d57f3a..cda6786 100644 --- a/shiftflow/models.py +++ b/shiftflow/models.py @@ -321,6 +321,7 @@ class WorkItem(models.Model): workshop = models.ForeignKey('shiftflow.Workshop', on_delete=models.PROTECT, null=True, blank=True, verbose_name='Цех') quantity_plan = models.PositiveIntegerField('В план, шт', default=0) + quantity_reported = models.PositiveIntegerField('Прогресс (оператор), шт', default=0) quantity_done = models.PositiveIntegerField('Сделано, шт', default=0) STATUS_CHOICES = [ diff --git a/shiftflow/templates/shiftflow/partials/_workitems_table.html b/shiftflow/templates/shiftflow/partials/_workitems_table.html index 6149180..8ff9a25 100644 --- a/shiftflow/templates/shiftflow/partials/_workitems_table.html +++ b/shiftflow/templates/shiftflow/partials/_workitems_table.html @@ -11,7 +11,7 @@ Материал Файлы Прогресс - План / Факт + План / Прогресс Статус @@ -64,7 +64,7 @@ {{ wi.quantity_plan }} / - {{ wi.quantity_done }} + {{ wi.quantity_reported|default:0 }} {% if wi.status == 'done' %} @@ -93,5 +93,11 @@ document.addEventListener("DOMContentLoaded", function() { if (href) window.location.href = href; }); }); + + document.querySelectorAll('.sf-item-progress').forEach(function (el) { + const w = parseInt(el.getAttribute('data-fact-width') || '0', 10) || 0; + const bar = el.querySelector('.sf-item-progress-bar'); + if (bar) bar.style.width = `${w}%`; + }); }); \ No newline at end of file diff --git a/shiftflow/templates/shiftflow/workitem_detail.html b/shiftflow/templates/shiftflow/workitem_detail.html index a5abf7b..f0bfbc0 100644 --- a/shiftflow/templates/shiftflow/workitem_detail.html +++ b/shiftflow/templates/shiftflow/workitem_detail.html @@ -41,7 +41,7 @@
{% csrf_token %} - +
@@ -93,11 +93,11 @@ {% endif %}
- Факт - {% if user_role in 'admin,technologist,master,operator' %} - + Прогресс + {% if user_role in 'admin,technologist,master,operator,prod_head' %} + {% else %} - {{ workitem.quantity_done }} шт. + {{ workitem.quantity_reported|default:0 }} шт. {% endif %}
@@ -279,7 +279,7 @@
Назад {% if user_role in 'admin,technologist,master,operator' %} - {% endif %} @@ -289,20 +289,44 @@
+{{ back_url|json_script:"wiBackUrl" }} {% endblock %} \ No newline at end of file diff --git a/shiftflow/views.py b/shiftflow/views.py index 52b02e1..bfdf865 100644 --- a/shiftflow/views.py +++ b/shiftflow/views.py @@ -562,9 +562,9 @@ class RegistryView(LoginRequiredMixin, ListView): workitems = list(work_qs.order_by('-date', 'deal__number', 'id')[:2000]) for wi in workitems: plan = int(wi.quantity_plan or 0) - done = int(wi.quantity_done or 0) + reported = int(getattr(wi, 'quantity_reported', 0) or 0) if plan > 0: - pct = int(round(done * 100 / plan)) + pct = int(round(reported * 100 / plan)) else: pct = 0 wi.fact_pct = pct @@ -770,7 +770,7 @@ class PaintingPlanAddView(LoginRequiredMixin, View): class WorkItemUpdateView(LoginRequiredMixin, View): def post(self, request, *args, **kwargs): profile = getattr(request.user, 'profile', None) - roles = get_user_roles(request.user) + roles = get_user_group_roles(request.user) role = primary_role(roles) is_readonly = bool(getattr(profile, 'is_readonly', False)) if profile else False @@ -828,7 +828,8 @@ class WorkItemUpdateView(LoginRequiredMixin, View): return redirect(next_url) qty_plan = parse_int(request.POST.get('quantity_plan')) - qty_done = parse_int(request.POST.get('quantity_done')) + qty_reported = parse_int(request.POST.get('quantity_reported')) + qty_done = parse_int(request.POST.get('quantity_done')) if role in ['admin', 'technologist'] else None workshop_id = parse_int(request.POST.get('workshop_id')) machine_id = parse_int(request.POST.get('machine_id')) date_raw = (request.POST.get('date') or '').strip() @@ -871,15 +872,14 @@ class WorkItemUpdateView(LoginRequiredMixin, View): wi.quantity_plan = qty_plan changed_fields.append('quantity_plan') - if qty_done is not None and qty_done >= 0: - # Комментарий: факт не должен превышать план по строке, иначе ломается «доступно к покраске». + if qty_reported is not None and qty_reported >= 0: plan_val = int((qty_plan if qty_plan is not None else wi.quantity_plan) or 0) - if plan_val > 0 and qty_done > plan_val: - messages.error(request, f'Факт ({qty_done}) не может быть больше плана ({plan_val}).') + if plan_val > 0 and qty_reported > plan_val: + messages.error(request, f'Прогресс ({qty_reported}) не может быть больше плана ({plan_val}).') return redirect(next_url) - wi.quantity_done = qty_done - changed_fields.append('quantity_done') + wi.quantity_reported = qty_reported + changed_fields.append('quantity_reported') if machine_id is not None and role in ['admin', 'technologist', 'master']: wi.machine_id = machine_id @@ -1399,17 +1399,18 @@ class WorkItemDetailView(LoginRequiredMixin, TemplateView): template_name = 'shiftflow/workitem_detail.html' def dispatch(self, request, *args, **kwargs): - roles = get_user_roles(request.user) + roles = get_user_group_roles(request.user) if not has_any_role(roles, ['admin', 'technologist', 'master', 'clerk', 'operator', '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_group_roles(self.request.user) + role = primary_role(roles) ctx['user_role'] = role - ctx['can_edit_entity'] = role in ['admin', 'technologist'] + ctx['user_roles'] = sorted(roles) + ctx['can_edit_entity'] = has_any_role(roles, ['admin', 'technologist']) wi = get_object_or_404( WorkItem.objects.select_related(