ДО формируются под сделку
All checks were successful
Deploy MES Core / deploy (push) Successful in 12s
All checks were successful
Deploy MES Core / deploy (push) Successful in 12s
This commit is contained in:
@@ -15,6 +15,7 @@
|
|||||||
- Отгрузка: можно добавлять несколько сделок в одну сессию отгрузки, выбирать позиции и подтверждать общий список.
|
- Отгрузка: можно добавлять несколько сделок в одну сессию отгрузки, выбирать позиции и подтверждать общий список.
|
||||||
- Журнал отгрузки: добавлены фильтр по периоду (по умолчанию 2 недели) и поиск по сделкам (номер/описание/заказчик), убран столбец «Куда».
|
- Журнал отгрузки: добавлены фильтр по периоду (по умолчанию 2 недели) и поиск по сделкам (номер/описание/заказчик), убран столбец «Куда».
|
||||||
- Списание / Производство: в блоках «Списано» и «Остаток ДО» выводится масса материалов (по размерам и «Масса на ед. учёта»); если масса не задана — показывается прочерк.
|
- Списание / Производство: в блоках «Списано» и «Остаток ДО» выводится масса материалов (по размерам и «Масса на ед. учёта»); если масса не задана — показывается прочерк.
|
||||||
|
- Закрытие: деловой остаток (ДО) может наследовать сделку от списанного сырья (отключается чекбоксом) и доступен к отгрузке как сырьё по сделке.
|
||||||
- Реестр заданий: комментарий сменного задания/операции отображается под наименованием.
|
- Реестр заданий: комментарий сменного задания/операции отображается под наименованием.
|
||||||
- Паспорта изделий/компонентов: ссылки на PDF/DXF/картинки отображаются иконками и открываются в новой вкладке.
|
- Паспорта изделий/компонентов: ссылки на PDF/DXF/картинки отображаются иконками и открываются в новой вкладке.
|
||||||
- Паспорта изделий/сборок: блок «Состав» перенесён в верхнюю часть страницы, в таблицу состава добавлена колонка «Файлы».
|
- Паспорта изделий/сборок: блок «Состав» перенесён в верхнюю часть страницы, в таблицу состава добавлена колонка «Файлы».
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ def apply_closing(
|
|||||||
item_actions: dict[int, dict],
|
item_actions: dict[int, dict],
|
||||||
consumptions: dict[int, float],
|
consumptions: dict[int, float],
|
||||||
remnants: list[dict],
|
remnants: list[dict],
|
||||||
|
inherit_deal_for_remnants: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
logger.info('apply_closing:start user=%s machine=%s material=%s items=%s consumptions=%s remnants=%s', user_id, machine_id, material_id, list(item_actions.keys()), list(consumptions.keys()), len(remnants))
|
logger.info('apply_closing:start user=%s machine=%s material=%s items=%s consumptions=%s remnants=%s', user_id, machine_id, material_id, list(item_actions.keys()), list(consumptions.keys()), len(remnants))
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ def apply_closing(
|
|||||||
)
|
)
|
||||||
|
|
||||||
logger.info('apply_closing:close_session id=%s', report.id)
|
logger.info('apply_closing:close_session id=%s', report.id)
|
||||||
close_cutting_session(report.id)
|
close_cutting_session(report.id, inherit_deal_for_remnants=bool(inherit_deal_for_remnants))
|
||||||
|
|
||||||
for it in items:
|
for it in items:
|
||||||
spec = item_actions.get(it.id) or {}
|
spec = item_actions.get(it.id) or {}
|
||||||
@@ -142,6 +143,7 @@ def apply_closing_workitems(
|
|||||||
item_actions: dict[int, dict], # workitem_id -> {'action': 'done'|'partial', 'fact': int}
|
item_actions: dict[int, dict], # workitem_id -> {'action': 'done'|'partial', 'fact': int}
|
||||||
consumptions: dict[int, float],
|
consumptions: dict[int, float],
|
||||||
remnants: list[dict],
|
remnants: list[dict],
|
||||||
|
inherit_deal_for_remnants: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
logger.info('apply_closing_workitems:start user=%s machine=%s material=%s workitems=%s cons=%s rem=%s', user_id, machine_id, material_id, list(item_actions.keys()), list(consumptions.keys()), len(remnants))
|
logger.info('apply_closing_workitems:start user=%s machine=%s material=%s workitems=%s cons=%s rem=%s', user_id, machine_id, material_id, list(item_actions.keys()), list(consumptions.keys()), len(remnants))
|
||||||
|
|
||||||
@@ -224,5 +226,5 @@ def apply_closing_workitems(
|
|||||||
unique_id=None,
|
unique_id=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
close_cutting_session(report.id)
|
close_cutting_session(report.id, inherit_deal_for_remnants=bool(inherit_deal_for_remnants))
|
||||||
logger.info('apply_closing_workitems:done report=%s shift_items=%s', report.id, created_shift)
|
logger.info('apply_closing_workitems:done report=%s shift_items=%s', report.id, created_shift)
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ logger = logging.getLogger('mes')
|
|||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def close_cutting_session(session_id: int) -> None:
|
def close_cutting_session(session_id: int, *, inherit_deal_for_remnants: bool = False) -> None:
|
||||||
"""
|
"""
|
||||||
Закрытие CuttingSession (транзакция склада).
|
Закрытие CuttingSession (транзакция склада).
|
||||||
|
|
||||||
@@ -56,6 +56,8 @@ def close_cutting_session(session_id: int) -> None:
|
|||||||
raise RuntimeError('Не задан склад цеха для станка (Цех -> Склад цеха).')
|
raise RuntimeError('Не задан склад цеха для станка (Цех -> Склад цеха).')
|
||||||
|
|
||||||
consumed_material_ids: set[int] = set()
|
consumed_material_ids: set[int] = set()
|
||||||
|
consumed_deal_ids: set[int] = set()
|
||||||
|
consumed_is_customer_supplied = False
|
||||||
|
|
||||||
consumptions = list(
|
consumptions = list(
|
||||||
ProductionReportConsumption.objects.select_related('material', 'stock_item', 'stock_item__material', 'stock_item__location')
|
ProductionReportConsumption.objects.select_related('material', 'stock_item', 'stock_item__material', 'stock_item__location')
|
||||||
@@ -72,6 +74,11 @@ def close_cutting_session(session_id: int) -> None:
|
|||||||
si = StockItem.objects.select_for_update(of=('self',)).select_related('material', 'location').get(pk=c.stock_item_id)
|
si = StockItem.objects.select_for_update(of=('self',)).select_related('material', 'location').get(pk=c.stock_item_id)
|
||||||
logger.info('close_cutting_session:consume stock_item=%s qty=%s before=%s', si.id, c.quantity, si.quantity)
|
logger.info('close_cutting_session:consume stock_item=%s qty=%s before=%s', si.id, c.quantity, si.quantity)
|
||||||
|
|
||||||
|
if getattr(si, 'deal_id', None):
|
||||||
|
consumed_deal_ids.add(int(si.deal_id))
|
||||||
|
if bool(getattr(si, 'is_customer_supplied', False)):
|
||||||
|
consumed_is_customer_supplied = True
|
||||||
|
|
||||||
if not si.material_id:
|
if not si.material_id:
|
||||||
raise RuntimeError('В списании сырья указана позиция склада без material.')
|
raise RuntimeError('В списании сырья указана позиция склада без material.')
|
||||||
|
|
||||||
@@ -129,6 +136,11 @@ def close_cutting_session(session_id: int) -> None:
|
|||||||
|
|
||||||
used = StockItem.objects.select_for_update(of=('self',)).select_related('material', 'location').get(pk=session.used_stock_item_id)
|
used = StockItem.objects.select_for_update(of=('self',)).select_related('material', 'location').get(pk=session.used_stock_item_id)
|
||||||
logger.info('close_cutting_session:used stock_item=%s before=%s', used.id, used.quantity)
|
logger.info('close_cutting_session:used stock_item=%s before=%s', used.id, used.quantity)
|
||||||
|
|
||||||
|
if getattr(used, 'deal_id', None):
|
||||||
|
consumed_deal_ids.add(int(used.deal_id))
|
||||||
|
if bool(getattr(used, 'is_customer_supplied', False)):
|
||||||
|
consumed_is_customer_supplied = True
|
||||||
if not used.material_id:
|
if not used.material_id:
|
||||||
raise RuntimeError('Взятый материал должен ссылаться на сырьё (material), а не на готовую деталь (entity).')
|
raise RuntimeError('Взятый материал должен ссылаться на сырьё (material), а не на готовую деталь (entity).')
|
||||||
|
|
||||||
@@ -191,13 +203,19 @@ def close_cutting_session(session_id: int) -> None:
|
|||||||
)
|
)
|
||||||
ProductionReportStockResult.objects.create(report=session, stock_item=created, kind='finished')
|
ProductionReportStockResult.objects.create(report=session, stock_item=created, kind='finished')
|
||||||
|
|
||||||
|
remnant_deal_id = None
|
||||||
|
if inherit_deal_for_remnants and len(consumed_deal_ids) == 1:
|
||||||
|
remnant_deal_id = int(next(iter(consumed_deal_ids)))
|
||||||
|
|
||||||
remnants = list(ProductionReportRemnant.objects.filter(report=session).select_related('material'))
|
remnants = list(ProductionReportRemnant.objects.filter(report=session).select_related('material'))
|
||||||
for r in remnants:
|
for r in remnants:
|
||||||
created = StockItem.objects.create(
|
created = StockItem.objects.create(
|
||||||
material=r.material,
|
material=r.material,
|
||||||
|
deal_id=remnant_deal_id,
|
||||||
location=work_location,
|
location=work_location,
|
||||||
quantity=float(r.quantity),
|
quantity=float(r.quantity),
|
||||||
is_remnant=True,
|
is_remnant=True,
|
||||||
|
is_customer_supplied=bool(consumed_is_customer_supplied),
|
||||||
current_length=r.current_length,
|
current_length=r.current_length,
|
||||||
current_width=r.current_width,
|
current_width=r.current_width,
|
||||||
unique_id=r.unique_id,
|
unique_id=r.unique_id,
|
||||||
|
|||||||
@@ -140,7 +140,6 @@ def build_shipment_rows(
|
|||||||
is_archived=False,
|
is_archived=False,
|
||||||
quantity__gt=0,
|
quantity__gt=0,
|
||||||
material_id__isnull=False,
|
material_id__isnull=False,
|
||||||
is_customer_supplied=True,
|
|
||||||
)
|
)
|
||||||
.exclude(location_id=int(shipping_location_id))
|
.exclude(location_id=int(shipping_location_id))
|
||||||
.values('material_id')
|
.values('material_id')
|
||||||
@@ -264,7 +263,6 @@ def create_shipment_transfers(
|
|||||||
is_archived=False,
|
is_archived=False,
|
||||||
quantity__gt=0,
|
quantity__gt=0,
|
||||||
material_id=int(mat_id),
|
material_id=int(mat_id),
|
||||||
is_customer_supplied=True,
|
|
||||||
).select_related('material', 'location'),
|
).select_related('material', 'location'),
|
||||||
float(q),
|
float(q),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -132,7 +132,13 @@
|
|||||||
|
|
||||||
<div class="card shadow border-secondary">
|
<div class="card shadow border-secondary">
|
||||||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||||
|
<div class="d-flex flex-wrap gap-3 align-items-center">
|
||||||
<h5 class="mb-0">Остаток ДО</h5>
|
<h5 class="mb-0">Остаток ДО</h5>
|
||||||
|
<div class="form-check mb-0">
|
||||||
|
<input class="form-check-input" type="checkbox" name="inherit_deal_for_remnants" id="inheritDealForRemnants" value="1" checked>
|
||||||
|
<label class="form-check-label" for="inheritDealForRemnants">Привязать ДО к сделке сырья</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button type="button" class="btn btn-outline-accent btn-sm" id="addRemnantBtn" {% if not can_edit %}disabled{% endif %}>Добавить ДО</button>
|
<button type="button" class="btn btn-outline-accent btn-sm" id="addRemnantBtn" {% if not can_edit %}disabled{% endif %}>Добавить ДО</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
|
|||||||
@@ -135,7 +135,13 @@
|
|||||||
|
|
||||||
<div class="card shadow border-secondary">
|
<div class="card shadow border-secondary">
|
||||||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||||
|
<div class="d-flex flex-wrap gap-3 align-items-center">
|
||||||
<h5 class="mb-0">Остаток ДО</h5>
|
<h5 class="mb-0">Остаток ДО</h5>
|
||||||
|
<div class="form-check mb-0">
|
||||||
|
<input class="form-check-input" type="checkbox" name="inherit_deal_for_remnants" id="inheritDealForRemnants" value="1" checked>
|
||||||
|
<label class="form-check-label" for="inheritDealForRemnants">Привязать ДО к сделке сырья</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button type="button" class="btn btn-outline-accent btn-sm" id="addRemnantBtn" {% if not can_edit %}disabled{% endif %}>Добавить ДО</button>
|
<button type="button" class="btn btn-outline-accent btn-sm" id="addRemnantBtn" {% if not can_edit %}disabled{% endif %}>Добавить ДО</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="table-responsive">
|
||||||
|
|||||||
@@ -57,6 +57,35 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="border-top border-secondary"></div>
|
||||||
|
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-hover align-middle mb-0">
|
||||||
|
<thead>
|
||||||
|
<tr class="table-custom-header">
|
||||||
|
<th data-sort="false" style="width:44px;"></th>
|
||||||
|
<th>Сырьё</th>
|
||||||
|
<th class="text-center" style="width:120px;">Доступно</th>
|
||||||
|
<th class="text-center" style="width:180px;">К отгрузке</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for r in b.material_rows %}
|
||||||
|
<tr>
|
||||||
|
<td class="text-center"><input class="form-check-input border-secondary js-pick" type="checkbox" data-target="#qty_d{{ b.deal.id }}_mat_{{ r.material.id }}"></td>
|
||||||
|
<td><div class="fw-bold">{{ r.material.full_name|default:r.material.name }}</div><div class="small text-muted">Сырьё</div></td>
|
||||||
|
<td class="text-center fw-bold">{{ r.available|floatformat:"-g" }}</td>
|
||||||
|
<td class="text-center">
|
||||||
|
<input id="qty_d{{ b.deal.id }}_mat_{{ r.material.id }}" class="form-control bg-body text-body border-secondary ship-qty" type="number" min="0" step="0.001" max="{{ r.available|floatformat:'-g' }}" name="d{{ b.deal.id }}_mat_{{ r.material.id }}" value="0" data-deal="№{{ b.deal.number }}" data-label="Сырьё: {{ r.material.full_name|default:r.material.name }}" disabled>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% empty %}
|
||||||
|
<tr><td colspan="4" class="text-center text-muted py-4">Нет сырья к отгрузке</td></tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@@ -5350,6 +5350,8 @@ class ClosingView(LoginRequiredMixin, TemplateView):
|
|||||||
messages.error(request, 'Заполни списание: укажи, какие единицы на складе использованы и в каком количестве.')
|
messages.error(request, 'Заполни списание: укажи, какие единицы на складе использованы и в каком количестве.')
|
||||||
return redirect(f"{reverse_lazy('closing')}?machine_id={machine_id}&material_id={material_id}")
|
return redirect(f"{reverse_lazy('closing')}?machine_id={machine_id}&material_id={material_id}")
|
||||||
|
|
||||||
|
inherit_deal_for_remnants = bool(request.POST.get('inherit_deal_for_remnants'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apply_closing_workitems(
|
apply_closing_workitems(
|
||||||
user_id=request.user.id,
|
user_id=request.user.id,
|
||||||
@@ -5358,6 +5360,7 @@ class ClosingView(LoginRequiredMixin, TemplateView):
|
|||||||
item_actions=item_actions,
|
item_actions=item_actions,
|
||||||
consumptions=consumptions,
|
consumptions=consumptions,
|
||||||
remnants=remnants,
|
remnants=remnants,
|
||||||
|
inherit_deal_for_remnants=inherit_deal_for_remnants,
|
||||||
)
|
)
|
||||||
messages.success(request, 'Закрытие выполнено.')
|
messages.success(request, 'Закрытие выполнено.')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -5969,6 +5972,8 @@ class LegacyClosingView(LoginRequiredMixin, TemplateView):
|
|||||||
if idx > 60:
|
if idx > 60:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
inherit_deal_for_remnants = bool(request.POST.get('inherit_deal_for_remnants'))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
apply_closing(
|
apply_closing(
|
||||||
user_id=request.user.id,
|
user_id=request.user.id,
|
||||||
@@ -5977,6 +5982,7 @@ class LegacyClosingView(LoginRequiredMixin, TemplateView):
|
|||||||
item_actions=item_actions,
|
item_actions=item_actions,
|
||||||
consumptions=consumptions,
|
consumptions=consumptions,
|
||||||
remnants=remnants,
|
remnants=remnants,
|
||||||
|
inherit_deal_for_remnants=inherit_deal_for_remnants,
|
||||||
)
|
)
|
||||||
messages.success(request, 'Сохранено.')
|
messages.success(request, 'Сохранено.')
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in New Issue
Block a user