@@ -311,6 +319,7 @@ document.addEventListener('DOMContentLoaded', function () {
const dealNumber = document.getElementById('dealNumber');
const dealCompany = document.getElementById('dealCompany');
const dealDescription = document.getElementById('dealDescription');
+ const dealStatus = document.getElementById('dealStatus');
const dealSaveBtn = document.getElementById('dealSaveBtn');
const companyModal = document.getElementById('companyModal');
@@ -362,7 +371,13 @@ document.addEventListener('DOMContentLoaded', function () {
if (fillFromPdf) fillFromPdf.disabled = !hasPdf;
}
- if (drawingFile) drawingFile.addEventListener('change', updateFileButtons);
+ if (drawingFile) drawingFile.addEventListener('change', function () {
+ updateFileButtons();
+ if (!drawingName) return;
+ if (drawingName.value && drawingName.value.trim() !== '') return;
+ if (!drawingFile.files || drawingFile.files.length === 0) return;
+ drawingName.value = filenameBase(drawingFile.files[0].name);
+ });
if (extraDrawing) extraDrawing.addEventListener('change', updateFileButtons);
updateFileButtons();
@@ -459,6 +474,7 @@ document.addEventListener('DOMContentLoaded', function () {
dealNumber.value = '';
dealCompany.value = '';
dealDescription.value = '';
+ if (dealStatus) dealStatus.value = 'work';
if (mode === 'edit' && dealSelect && dealSelect.value) {
const data = await getJson(`/planning/deal/${dealSelect.value}/json/`);
@@ -466,6 +482,7 @@ document.addEventListener('DOMContentLoaded', function () {
dealNumber.value = data.number || '';
dealCompany.value = data.company_id ? String(data.company_id) : '';
dealDescription.value = data.description || '';
+ if (dealStatus) dealStatus.value = data.status || 'work';
}
});
}
@@ -476,6 +493,7 @@ document.addEventListener('DOMContentLoaded', function () {
id: dealId.value,
number: dealNumber.value,
company_id: dealCompany.value,
+ status: dealStatus ? dealStatus.value : 'work',
description: dealDescription.value,
};
const data = await postForm('{% url "deal_upsert" %}', payload);
diff --git a/shiftflow/urls.py b/shiftflow/urls.py
index dd404d9..8263a24 100644
--- a/shiftflow/urls.py
+++ b/shiftflow/urls.py
@@ -2,6 +2,7 @@ from django.urls import path
from .views import (
CompanyUpsertView,
DealDetailView,
+ DealPlanningView,
DealUpsertView,
IndexView,
ItemUpdateView,
@@ -24,6 +25,7 @@ urlpatterns = [
path('registry/', RegistryView.as_view(), name='registry'),
# Планирование
path('planning/', PlanningView.as_view(), name='planning'),
+ path('planning/deal//', DealPlanningView.as_view(), name='planning_deal'),
path('planning/add/', PlanningAddView.as_view(), name='planning_add'),
path('planning/task/add/', ProductionTaskCreateView.as_view(), name='task_add'),
path('planning/deal//json/', DealDetailView.as_view(), name='deal_json'),
diff --git a/shiftflow/views.py b/shiftflow/views.py
index 211d7ea..7f1e3f2 100644
--- a/shiftflow/views.py
+++ b/shiftflow/views.py
@@ -232,7 +232,37 @@ class PlanningView(LoginRequiredMixin, TemplateView):
role = profile.role if profile else ('admin' if self.request.user.is_superuser else 'operator')
context['user_role'] = role
- tasks = ProductionTask.objects.select_related('deal', 'material').annotate(
+ 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 = ProductionTask.objects.filter(deal=deal).select_related('material').annotate(
done_qty=Coalesce(Sum('items__quantity_fact'), 0),
planned_qty=Coalesce(
Sum(
@@ -249,7 +279,7 @@ class PlanningView(LoginRequiredMixin, TemplateView):
F('quantity_ordered') - F('done_qty') - F('planned_qty'),
output_field=IntegerField(),
)
- )
+ ).order_by('-id')
context['tasks'] = tasks
context['machines'] = Machine.objects.all()
@@ -284,6 +314,10 @@ class PlanningAddView(LoginRequiredMixin, View):
is_synced_1c=False,
)
+ next_url = request.POST.get('next') or ''
+ if next_url.startswith('/planning/deal/'):
+ return redirect(next_url)
+
return redirect('planning')
@@ -292,6 +326,13 @@ class ProductionTaskCreateView(LoginRequiredMixin, FormView):
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')
@@ -339,6 +380,7 @@ class DealDetailView(LoginRequiredMixin, View):
return JsonResponse({
'id': deal.id,
'number': deal.number,
+ 'status': deal.status,
'company_id': deal.company_id,
'description': deal.description or '',
})
@@ -355,6 +397,7 @@ class DealUpsertView(LoginRequiredMixin, View):
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)
@@ -365,6 +408,11 @@ class DealUpsertView(LoginRequiredMixin, View):
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)