diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5cc88e3..3776930 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -13,7 +13,7 @@
### Changed
- Производственные задачи и прогресс техпроцесса ведутся в разрезе партий поставки (серий) для одной сделки.
-- Улучшено сообщение о блокировке запуска «В производство» при отсутствии техпроцесса: показывается модалка и отдельная страница со списком проблемных позиций.
+- Улучшено сообщение о блокировке запуска «В производство» при отсутствии техпроцесса или материала: показывается модалка (для техпроцесса также есть отдельная страница) со списком проблемных позиций.
### Fixed
- Запуск «В производство» блокируется, если в BOM есть узлы без техпроцесса (EntityOperation seq=1), чтобы компоненты не попадали в «без техпроцесса».
diff --git a/shiftflow/templates/shiftflow/planning_deal.html b/shiftflow/templates/shiftflow/planning_deal.html
index 597e092..ea00a7f 100644
--- a/shiftflow/templates/shiftflow/planning_deal.html
+++ b/shiftflow/templates/shiftflow/planning_deal.html
@@ -564,6 +564,49 @@
{% endif %}
+{% if missing_material_rows %}
+
@@ -1119,5 +1162,17 @@ document.addEventListener('DOMContentLoaded', function () {
});
{% endif %}
+{% if missing_material_autoshow and missing_material_rows %}
+document.addEventListener('DOMContentLoaded', function () {
+ const el = document.getElementById('missingMaterialModal');
+ if (!el) return;
+ try {
+ const m = new bootstrap.Modal(el);
+ m.show();
+ } catch (e) {
+ }
+});
+{% endif %}
+
{% endblock %}
diff --git a/shiftflow/views.py b/shiftflow/views.py
index a2e1d43..d969f01 100644
--- a/shiftflow/views.py
+++ b/shiftflow/views.py
@@ -2022,29 +2022,59 @@ class DealPlanningView(LoginRequiredMixin, TemplateView):
deal = get_object_or_404(Deal.objects.select_related('company'), pk=self.kwargs['pk'])
context['deal'] = deal
- missing_ctx = None
+ context['missing_tech_process_rows'] = []
+ context['missing_tech_process_details_url'] = ''
+ context['missing_tech_process_autoshow'] = False
+
raw_missing = self.request.session.get('sf_missing_tech_process')
if raw_missing and int(raw_missing.get('deal_id') or 0) == int(deal.id):
- entity_ids = [int(x) for x in (raw_missing.get('entity_ids') or []) if str(x).isdigit() or isinstance(x, int)]
+ entity_ids = [
+ int(x)
+ for x in (raw_missing.get('entity_ids') or [])
+ if isinstance(x, int) or str(x).isdigit()
+ ]
qs = ProductEntity.objects.filter(id__in=entity_ids).order_by('entity_type', 'drawing_number', 'name', 'id')
- items = []
+ next_qs = urlencode({'next': self.request.get_full_path()})
+ rows = []
for e in qs:
- items.append({
+ rows.append({
'entity': e,
- 'url': f"{reverse_lazy('product_info', kwargs={'pk': int(e.id)})}?next={self.request.get_full_path()}" ,
+ 'url': f"{reverse_lazy('product_info', kwargs={'pk': int(e.id)})}?{next_qs}",
})
- missing_ctx = {
- 'items': items,
- 'details_url': str(reverse_lazy('deal_missing_tech_process', kwargs={'pk': int(deal.id)})),
- 'autoshow': not bool(raw_missing.get('shown')),
- }
+ context['missing_tech_process_rows'] = rows
+ context['missing_tech_process_details_url'] = str(reverse_lazy('deal_missing_tech_process', kwargs={'pk': int(deal.id)}))
+ context['missing_tech_process_autoshow'] = not bool(raw_missing.get('shown'))
if not bool(raw_missing.get('shown')):
raw_missing['shown'] = True
self.request.session['sf_missing_tech_process'] = raw_missing
- context['missing_tech_process'] = missing_ctx
+ context['missing_material_rows'] = []
+ context['missing_material_autoshow'] = False
+
+ raw_mat = self.request.session.get('sf_missing_material')
+ if raw_mat and int(raw_mat.get('deal_id') or 0) == int(deal.id):
+ entity_ids = [
+ int(x)
+ for x in (raw_mat.get('entity_ids') or [])
+ if isinstance(x, int) or str(x).isdigit()
+ ]
+ qs = ProductEntity.objects.filter(id__in=entity_ids).order_by('entity_type', 'drawing_number', 'name', 'id')
+ next_qs = urlencode({'next': self.request.get_full_path()})
+ rows = []
+ for e in qs:
+ rows.append({
+ 'entity': e,
+ 'url': f"{reverse_lazy('product_info', kwargs={'pk': int(e.id)})}?{next_qs}",
+ })
+
+ context['missing_material_rows'] = rows
+ context['missing_material_autoshow'] = not bool(raw_mat.get('shown'))
+
+ if not bool(raw_mat.get('shown')):
+ raw_mat['shown'] = True
+ self.request.session['sf_missing_material'] = raw_mat
di = list(
DealItem.objects.select_related('entity', 'entity__assembly_passport')
@@ -3514,13 +3544,14 @@ class DealBatchActionView(LoginRequiredMixin, View):
)
return redirect(next_url)
- bad = list(ProductEntity.objects.filter(id__in=list(getattr(ev, 'missing_material_ids', []) or [])).values_list('drawing_number', 'name'))
- if bad:
- preview = ", ".join([f"{dn or '—'} {nm}" for dn, nm in bad[:5]])
- more = '' if len(bad) <= 5 else f" и ещё {len(bad)-5}"
- messages.error(request, f'В спецификации есть детали без материала: {preview}{more}. Добавь material в паспорт(ы) и повтори запуск.')
- else:
- messages.error(request, 'В спецификации есть детали без материала. Добавь material и повтори запуск.')
+ missing_ids = sorted({int(x) for x in (getattr(ev, 'missing_material_ids', []) or [])})
+ if missing_ids:
+ request.session['sf_missing_material'] = {
+ 'deal_id': int(deal_id),
+ 'entity_ids': missing_ids,
+ 'shown': False,
+ }
+ messages.error(request, 'В спецификации есть детали без материала. Добавь material в паспорт(ы) и повтори запуск.')
return redirect(next_url)
except Exception:
logger.exception('start_batch_item_production: failed deal_id=%s item_id=%s qty=%s', deal_id, item_id, qty)