All checks were successful
Deploy MES Core / deploy (push) Successful in 4m16s
1054 lines
58 KiB
HTML
1054 lines
58 KiB
HTML
{% extends 'base.html' %}
|
||
|
||
{% block content %}
|
||
<div class="card shadow border-secondary mb-3">
|
||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<h3 class="text-accent mb-1">
|
||
<i class="bi bi-briefcase me-2"></i>Сделка {{ deal.number }}
|
||
</h3>
|
||
<div class="small text-muted">
|
||
{% if deal.company %}{{ deal.company.name }}{% else %}—{% endif %}
|
||
{% if deal.description %} · {{ deal.description }}{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-flex gap-2">
|
||
<span class="badge {% if deal.status == 'work' %}bg-primary{% elif deal.status == 'done' %}bg-success{% else %}bg-secondary{% endif %} align-self-center">
|
||
{{ deal.get_status_display }}
|
||
</span>
|
||
|
||
{% if deal.status == 'lead' and user_role in 'admin,prod_head,technologist,master,clerk' %}
|
||
<form method="post" class="d-inline">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="set_work">
|
||
<button type="submit" class="btn btn-outline-accent btn-sm">
|
||
<i class="bi bi-arrow-right-circle me-1"></i>В работу
|
||
</button>
|
||
</form>
|
||
{% endif %}
|
||
|
||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'planning' %}">
|
||
<i class="bi bi-arrow-left me-1"></i>Назад
|
||
</a>
|
||
{% if user_role in 'admin,prod_head,technologist,master' %}
|
||
<form method="post" class="d-inline">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="explode_deal">
|
||
<button type="submit" class="btn btn-outline-warning btn-sm" title="Пересчитать потребности снабжения">
|
||
<i class="bi bi-lightning me-1"></i>Вскрыть BOM
|
||
</button>
|
||
</form>
|
||
{% endif %}
|
||
{% if user_role in 'admin,clerk,manager,prod_head,technologist' %}
|
||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'shipping' %}?deal_id={{ deal.id }}">
|
||
<i class="bi bi-truck me-1"></i>Отгрузка
|
||
</a>
|
||
{% endif %}
|
||
|
||
{% if user_role in 'admin,technologist,manager,prod_head' %}
|
||
<button type="button" class="btn btn-outline-accent btn-sm" data-bs-toggle="modal" data-bs-target="#dealItemModal">
|
||
<i class="bi bi-plus-lg me-1"></i>Добавить задание
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-body p-0">
|
||
|
||
<div class="p-3">
|
||
<div class="card border-secondary mb-3">
|
||
<div class="card-header border-secondary py-2 d-flex justify-content-between align-items-center">
|
||
<strong>Позиции сделки</strong>
|
||
<div class="small text-muted">Изделие / СБ / Деталь</div>
|
||
</div>
|
||
|
||
<div class="table-responsive">
|
||
<table class="table table-hover mb-0 align-middle" data-sortable="1">
|
||
<thead>
|
||
<tr class="table-custom-header">
|
||
<th>Позиция</th>
|
||
<th data-sort="false" style="width: 160px;">Прогресс</th>
|
||
<th class="text-center">Заказано / Сделано / В плане</th>
|
||
<th class="text-center">Осталось</th>
|
||
<th data-sort="false" class="text-end">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for it in deal_items %}
|
||
<tr class="deal-entity-row" role="button" data-href="{% url 'product_info' it.entity.id %}?next={{ request.get_full_path|urlencode }}">
|
||
<td>
|
||
<div class="fw-bold">{{ it.entity.drawing_number|default:"—" }} {{ it.entity.name }}</div>
|
||
<div class="small text-muted">{{ it.entity.get_entity_type_display }}</div>
|
||
</td>
|
||
<td>
|
||
<div class="progress bg-secondary-subtle border border-secondary sf-progress" style="height: 10px;" data-done-width="{{ it.done_width }}" data-plan-width="{{ it.plan_width }}" title="Сделано: {{ it.done_qty }} · В плане: {{ it.planned_qty }}">
|
||
<div class="progress-bar bg-success sf-progress-done"></div>
|
||
<div class="progress-bar bg-warning sf-progress-plan"></div>
|
||
</div>
|
||
</td>
|
||
<td class="text-center">
|
||
<span class="text-info fw-bold">{{ it.quantity }}</span> /
|
||
<span class="text-success">{{ it.done_qty }}</span> /
|
||
<span class="text-warning">{{ it.planned_qty }}</span>
|
||
</td>
|
||
<td class="text-center">{{ it.remaining_qty }}</td>
|
||
<td class="text-end" onclick="event.stopPropagation();">
|
||
<div class="d-flex justify-content-end gap-1 flex-wrap" onclick="event.stopPropagation();">
|
||
{% if user_role in 'admin,technologist,manager,prod_head' %}
|
||
<button
|
||
type="button"
|
||
class="btn btn-outline-accent btn-sm"
|
||
data-bs-toggle="modal"
|
||
data-bs-target="#startProductionModal"
|
||
data-entity-id="{{ it.entity.id }}"
|
||
data-entity-label="{{ it.entity.drawing_number|default:'—' }} {{ it.entity.name }}"
|
||
>
|
||
<i class="bi bi-play-fill me-1"></i>В производство
|
||
</button>
|
||
|
||
<form method="post" action="{% url 'deal_item_upsert' %}" class="d-inline-flex gap-1 align-items-center" onclick="event.stopPropagation();">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="set_qty">
|
||
<input type="hidden" name="deal_id" value="{{ deal.id }}">
|
||
<input type="hidden" name="entity_id" value="{{ it.entity.id }}">
|
||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||
<input class="form-control form-control-sm bg-body text-body border-secondary" style="width:90px;" type="number" min="1" name="quantity" value="{{ it.quantity }}" title="Кол-во по сделке" required>
|
||
<button class="btn btn-outline-secondary btn-sm" type="submit" title="Обновить количество">OK</button>
|
||
</form>
|
||
|
||
<button
|
||
type="button"
|
||
class="btn btn-outline-danger btn-sm"
|
||
data-bs-toggle="modal"
|
||
data-bs-target="#dealItemDeleteModal"
|
||
data-deal-id="{{ deal.id }}"
|
||
data-entity-id="{{ it.entity.id }}"
|
||
data-next="{{ request.get_full_path }}"
|
||
data-entity-label="{{ it.entity.drawing_number|default:'—' }} {{ it.entity.name }}"
|
||
title="Удалить из сделки"
|
||
>
|
||
<i class="bi bi-trash"></i>
|
||
</button>
|
||
{% else %}
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" disabled>В производство</button>
|
||
{% endif %}
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% empty %}
|
||
<tr><td colspan="5" class="text-center text-muted py-4">Пока нет позиций</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="p-3 pt-0">
|
||
<div class="card border-secondary mb-3">
|
||
<div class="card-header border-secondary py-2 d-flex justify-content-between align-items-center">
|
||
<strong>Партии поставки</strong>
|
||
{% if user_role in 'admin,technologist' %}
|
||
<button type="button" class="btn btn-outline-accent btn-sm" data-bs-toggle="modal" data-bs-target="#dealBatchModal">
|
||
<i class="bi bi-plus-lg me-1"></i>Добавить партию
|
||
</button>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="table-responsive">
|
||
<table class="table table-hover mb-0 align-middle">
|
||
<thead>
|
||
<tr class="table-custom-header">
|
||
<th style="width:160px;">Отгрузка</th>
|
||
<th>Партия</th>
|
||
<th style="width:220px;">Запущено</th>
|
||
<th>Состав партии</th>
|
||
<th data-sort="false" class="text-end"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for b in delivery_batches %}
|
||
<tr>
|
||
<td class="fw-bold">{{ b.due_date|date:"d.m.Y" }}</td>
|
||
<td>
|
||
{{ b.name|default:"—" }}
|
||
{% if b.is_default %}
|
||
<span class="badge bg-secondary ms-2">по умолчанию</span>
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
<div class="small text-muted mb-1">{{ b.total_started }} / {{ b.total_qty }} (осталось {{ b.total_remaining }})</div>
|
||
<div class="progress bg-secondary-subtle border border-secondary" style="height: 10px;">
|
||
<div class="progress-bar bg-warning" style="width: {{ b.started_pct }}%"></div>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
{% if b.items_list %}
|
||
<div class="table-responsive">
|
||
<table class="table table-sm mb-0 align-middle">
|
||
<thead>
|
||
<tr class="table-custom-header">
|
||
<th style="width:110px;">Тип</th>
|
||
<th style="width:160px;">Обозначение</th>
|
||
<th>Наименование</th>
|
||
<th class="text-center" style="width:80px;">Кол-во</th>
|
||
<th class="text-center" style="width:90px;">Запущено</th>
|
||
<th class="text-center" style="width:90px;">Осталось</th>
|
||
<th style="width:160px;">Прогресс</th>
|
||
<th data-sort="false" class="text-end" style="width:160px;"></th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for bi in b.items_list %}
|
||
<tr>
|
||
<td class="small text-muted">{{ bi.entity.get_entity_type_display }}</td>
|
||
<td class="fw-bold">{{ bi.entity.drawing_number|default:"—" }}</td>
|
||
<td>{{ bi.entity.name }}</td>
|
||
<td class="text-center">{{ bi.quantity }}</td>
|
||
<td class="text-center">{{ bi.started_qty }}</td>
|
||
<td class="text-center">{{ bi.remaining_to_start }}</td>
|
||
<td>
|
||
<div class="progress bg-secondary-subtle border border-secondary" style="height: 10px;">
|
||
<div class="progress-bar bg-warning" style="width: {{ bi.started_pct }}%"></div>
|
||
</div>
|
||
</td>
|
||
<td class="text-end">
|
||
{% if user_role in 'admin,technologist' %}
|
||
{% if bi.started_qty and bi.started_qty > 0 %}
|
||
<button
|
||
type="button"
|
||
class="btn btn-outline-warning btn-sm"
|
||
data-bs-toggle="modal"
|
||
data-bs-target="#rollbackProductionModal{{ bi.id }}"
|
||
title="Откатить запуск в производство"
|
||
>
|
||
<i class="bi bi-arrow-counterclockwise"></i>
|
||
</button>
|
||
{% endif %}
|
||
{% endif %}
|
||
|
||
{% if user_role in 'admin,technologist' and not b.is_default %}
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#dealBatchItemModal" data-batch-id="{{ b.id }}">Добавить</button>
|
||
<form method="post" action="{% url 'deal_batch_action' %}" class="d-inline">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="delete_batch_item">
|
||
<input type="hidden" name="deal_id" value="{{ deal.id }}">
|
||
<input type="hidden" name="item_id" value="{{ bi.id }}">
|
||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||
<button class="btn btn-outline-secondary btn-sm" type="submit">Удалить</button>
|
||
</form>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="text-muted">Пусто</div>
|
||
{% endif %}
|
||
|
||
{% for bi in b.items_list %}
|
||
{% if user_role in 'admin,technologist' and bi.started_qty and bi.started_qty > 0 %}
|
||
<div class="modal fade" id="rollbackProductionModal{{ bi.id }}" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<form method="post" action="{% url 'deal_batch_action' %}" class="modal-content border-secondary">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="rollback_batch_item_production">
|
||
<input type="hidden" name="deal_id" value="{{ deal.id }}">
|
||
<input type="hidden" name="item_id" value="{{ bi.id }}">
|
||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||
|
||
<div class="modal-header border-secondary">
|
||
<h5 class="modal-title">Откатить запуск</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="small text-muted mb-2">{{ bi.entity.drawing_number|default:"—" }} {{ bi.entity.name }}</div>
|
||
<div class="mb-3">
|
||
<label class="form-label">Сколько откатить, шт</label>
|
||
<input class="form-control bg-body text-body border-secondary" type="number" min="1" max="{{ bi.started_qty }}" name="quantity" value="{{ bi.started_qty }}" required>
|
||
<div class="form-text">Запущено в партии: {{ bi.started_qty }} шт</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer border-secondary">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button type="submit" class="btn btn-outline-warning">Откатить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
{% endfor %}
|
||
</td>
|
||
<td class="text-end">
|
||
{% if user_role in 'admin,technologist' and not b.is_default %}
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" data-bs-toggle="modal" data-bs-target="#dealBatchItemModal" data-batch-id="{{ b.id }}">Добавить</button>
|
||
<form method="post" action="{% url 'deal_batch_action' %}" class="d-inline">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="delete_batch">
|
||
<input type="hidden" name="deal_id" value="{{ deal.id }}">
|
||
<input type="hidden" name="batch_id" value="{{ b.id }}">
|
||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||
<button class="btn btn-outline-secondary btn-sm" type="submit">Удалить</button>
|
||
</form>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% empty %}
|
||
<tr><td colspan="5" class="text-center text-muted py-3">Партий пока нет</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% for g in workshop_task_groups %}
|
||
<div class="card border-secondary mb-3">
|
||
<div class="card-header border-secondary py-2 d-flex justify-content-between align-items-center">
|
||
<strong>{{ g.name }}</strong>
|
||
<div class="small text-muted">{{ g.tasks|length }} задач</div>
|
||
</div>
|
||
<div class="table-responsive">
|
||
<table class="table table-hover mb-0 align-middle" data-sortable="1">
|
||
<thead>
|
||
<tr class="table-custom-header">
|
||
<th>Позиция</th>
|
||
<th>Операция</th>
|
||
<th data-sort="false" style="width: 160px;">Прогресс</th>
|
||
<th class="text-center">Заказано / Сделано / В смене</th>
|
||
<th class="text-center">Осталось</th>
|
||
<th data-sort="false" class="text-center">Файлы</th>
|
||
<th data-sort="false" class="text-end">Действия</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for t in g.tasks %}
|
||
<tr class="task-row" style="cursor:pointer" {% if t.entity_id %}data-href="{% url 'product_info' t.entity_id %}?next={{ request.get_full_path|urlencode }}"{% endif %}>
|
||
<td>
|
||
<div class="fw-bold">
|
||
{% if t.entity %}
|
||
{{ t.entity.drawing_number|default:"—" }} {{ t.entity.name }}
|
||
{% else %}
|
||
{{ t.drawing_name|default:"Б/ч" }}
|
||
{% endif %}
|
||
</div>
|
||
<div class="small text-muted">
|
||
{% if t.material %}{{ t.material.full_name|default:t.material.name }}{% else %}—{% endif %}
|
||
</div>
|
||
</td>
|
||
<td class="small">{{ t.current_operation_name|default:"—" }}</td>
|
||
<td>
|
||
<div class="progress bg-secondary-subtle border border-secondary sf-progress" style="height: 10px;" data-done-width="{{ t.done_width }}" data-plan-width="{{ t.plan_width }}" title="Сделано: {{ t.done_pct }}% · В смене: {{ t.plan_pct }}%">
|
||
<div class="progress-bar bg-success sf-progress-done"></div>
|
||
<div class="progress-bar bg-warning sf-progress-plan"></div>
|
||
</div>
|
||
</td>
|
||
<td class="text-center">
|
||
<span class="text-info fw-bold">{{ t.quantity_ordered }}</span> /
|
||
<span class="text-success">{{ t.done_qty }}</span> /
|
||
<span class="text-warning">{{ t.planned_qty }}</span>
|
||
</td>
|
||
<td class="text-center">{{ t.remaining_qty }}</td>
|
||
<td class="text-center">
|
||
{% if t.drawing_file %}
|
||
<a href="{{ t.drawing_file.url }}" target="_blank" class="btn btn-sm btn-outline-info p-1 stop-prop" title="DXF/IGES">
|
||
<i class="bi bi-file-earmark-code"></i>
|
||
</a>
|
||
{% endif %}
|
||
{% if t.extra_drawing %}
|
||
<a href="{{ t.extra_drawing.url }}" target="_blank" class="btn btn-sm btn-outline-danger p-1 stop-prop" title="Чертеж PDF">
|
||
<i class="bi bi-file-pdf"></i>
|
||
</a>
|
||
{% endif %}
|
||
</td>
|
||
<td class="text-end">
|
||
{% if user_role in 'admin,technologist' %}
|
||
{% if t.current_operation_id and t.entity_id %}
|
||
<button
|
||
type="button"
|
||
class="btn btn-outline-accent btn-sm"
|
||
data-bs-toggle="modal"
|
||
data-bs-target="#workItemModal"
|
||
data-entity-id="{{ t.entity_id }}"
|
||
data-batch-id="{{ t.delivery_batch_id|default:'' }}"
|
||
data-operation-id="{{ t.current_operation_id }}"
|
||
data-workshop-id="{{ t.current_workshop_id|default:'' }}"
|
||
data-workshop-name="{{ t.current_workshop_name|default:'' }}"
|
||
data-task-name="{% if t.entity %}{{ t.entity.drawing_number|default:'—' }} {{ t.entity.name }}{% else %}{{ t.drawing_name|default:'Б/ч' }}{% endif %}"
|
||
data-operation-name="{{ t.current_operation_name|default:'' }}"
|
||
data-task-rem="{{ t.remaining_qty }}"
|
||
>
|
||
<i class="bi bi-plus-lg me-1"></i>В смену
|
||
</button>
|
||
{% else %}
|
||
<button type="button" class="btn btn-outline-secondary btn-sm" disabled>В смену</button>
|
||
{% endif %}
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% empty %}
|
||
<tr><td colspan="7" class="text-center p-4 text-muted">Задач нет</td></tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% empty %}
|
||
<div class="text-center p-5 text-muted">Задач нет</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="startProductionModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<form method="post" action="{% url 'deal_batch_action' %}" class="modal-content border-secondary">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="start_batch_item_production">
|
||
<input type="hidden" name="deal_id" value="{{ deal.id }}">
|
||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||
|
||
<div class="modal-header border-secondary">
|
||
<h5 class="modal-title">Запуск в производство</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="small text-muted mb-2" id="spTitle"></div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Партия</label>
|
||
<select class="form-select bg-body text-body border-secondary" name="item_id" id="spBatchItem" required>
|
||
{% for b in delivery_batches %}
|
||
{% for bi in b.items_list %}
|
||
{% if bi.remaining_to_start > 0 %}
|
||
<option value="{{ bi.id }}" data-entity-id="{{ bi.entity_id }}" data-rem="{{ bi.remaining_to_start }}">
|
||
{{ b.due_date|date:"d.m.Y" }}{% if b.name %} · {{ b.name }}{% endif %} — осталось {{ bi.remaining_to_start }} шт
|
||
</option>
|
||
{% endif %}
|
||
{% endfor %}
|
||
{% endfor %}
|
||
</select>
|
||
<div class="form-text">Если списка нет — сначала создай партию и добавь туда позицию сделки.</div>
|
||
</div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label">Количество, шт</label>
|
||
<input class="form-control bg-body text-body border-secondary" type="number" min="1" name="quantity" id="spQty" required>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer border-secondary">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button type="submit" class="btn btn-outline-accent" id="spSubmit">Запустить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="workItemModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<form method="post" action="{% url 'workitem_add' %}" class="modal-content border-secondary">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="deal_id" value="{{ deal.id }}">
|
||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||
|
||
<div class="modal-header border-secondary">
|
||
<h5 class="modal-title">Добавить в смену</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<input type="hidden" name="entity_id" id="wiEntityId">
|
||
<input type="hidden" name="delivery_batch_id" id="wiBatchId">
|
||
<input type="hidden" name="operation_id" id="wiOperationId">
|
||
|
||
<div class="small text-muted mb-2" id="wiTitle"></div>
|
||
<div class="small text-muted mb-3" id="wiOp"></div>
|
||
|
||
<input type="hidden" name="workshop_id" id="wiWorkshopId">
|
||
|
||
<div class="small text-muted mb-3" id="wiWorkshopLabel"></div>
|
||
|
||
<div class="mb-3">
|
||
<label class="form-label small text-muted d-block">Пост (опционально)</label>
|
||
<div class="d-flex flex-wrap gap-1" id="machineToggleGroup">
|
||
<input type="radio" class="btn-check" name="machine_id" id="m_none" value="">
|
||
<label class="btn btn-outline-secondary btn-sm" for="m_none">Без станка</label>
|
||
|
||
{% for m in machines %}
|
||
<input type="radio" class="btn-check" name="machine_id" id="m_{{ m.id }}" value="{{ m.id }}" data-workshop-id="{{ m.workshop_id|default:'' }}">
|
||
<label class="btn btn-outline-accent btn-sm" for="m_{{ m.id }}">{{ m.name }}</label>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="mb-2">
|
||
<label class="form-label small text-muted">Сколько в смену (шт)</label>
|
||
<input type="number" min="1" class="form-control border-secondary" name="quantity_plan" id="wiQty" required>
|
||
</div>
|
||
|
||
<div class="form-check mb-3">
|
||
<input class="form-check-input border-secondary" type="checkbox" name="recursive_bom" id="recursiveBomCheck" checked>
|
||
<label class="form-check-label small text-muted" for="recursiveBomCheck">
|
||
Включить в смену все дочерние компоненты (по всем операциям, строго по БОМу)
|
||
</label>
|
||
</div>
|
||
|
||
<div class="small text-muted" id="wiHint"></div>
|
||
</div>
|
||
<div class="modal-footer border-secondary">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button type="submit" class="btn btn-outline-accent" id="wiSubmit">Добавить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- productInfoModal удалён: паспорт компонента открывается отдельной страницей -->
|
||
<div class="d-none" id="productInfoModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-xl">
|
||
<div class="modal-content border-secondary">
|
||
<div class="modal-header border-secondary">
|
||
<h5 class="modal-title">Информация о компоненте</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="modal-body" id="productInfoBody">
|
||
<div class="text-muted">Загрузка...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="dealBatchModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<form method="post" action="{% url 'deal_batch_action' %}" class="modal-content border-secondary">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="create_batch">
|
||
<input type="hidden" name="deal_id" value="{{ deal.id }}">
|
||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||
|
||
<div class="modal-header border-secondary">
|
||
<h5 class="modal-title">Новая партия поставки</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="row g-2">
|
||
<div class="col-md-6">
|
||
<label class="form-label">Отгрузка</label>
|
||
<input class="form-control bg-body text-body border-secondary" type="date" name="due_date" required>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label">Название (опц.)</label>
|
||
<input class="form-control bg-body text-body border-secondary" name="name" placeholder="Напр. Партия 1">
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer border-secondary">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button type="submit" class="btn btn-outline-accent">Создать</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="dealBatchItemModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg">
|
||
<form method="post" action="{% url 'deal_batch_action' %}" class="modal-content border-secondary">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="add_batch_item">
|
||
<input type="hidden" name="deal_id" value="{{ deal.id }}">
|
||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||
|
||
<div class="modal-header border-secondary">
|
||
<h5 class="modal-title">Добавить в партию</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="row g-2 align-items-end">
|
||
<div class="col-md-4">
|
||
<label class="form-label">Партия</label>
|
||
<select class="form-select bg-body text-body border-secondary" name="batch_id" id="batchSelect" required>
|
||
{% for b in delivery_batches %}
|
||
{% if not b.is_default %}
|
||
<option value="{{ b.id }}">{{ b.due_date|date:"d.m.Y" }}{% if b.name %} · {{ b.name }}{% endif %}</option>
|
||
{% endif %}
|
||
{% empty %}
|
||
<option value="">Сначала создай партию</option>
|
||
{% endfor %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<label class="form-label">Позиция сделки</label>
|
||
<select class="form-select bg-body text-body border-secondary" name="entity_id" id="biEntitySelect" required {% if not deal_items %}disabled{% endif %}>
|
||
{% if deal_items %}
|
||
{% for it in deal_items %}
|
||
<option value="{{ it.entity.id }}" data-rem="{{ it.remaining_to_allocate|default:0 }}">{{ it.entity.drawing_number|default:"—" }} {{ it.entity.name }}</option>
|
||
{% endfor %}
|
||
{% else %}
|
||
<option value="">Сначала добавь позиции сделки</option>
|
||
{% endif %}
|
||
</select>
|
||
</div>
|
||
<div class="col-md-2">
|
||
<label class="form-label">Кол-во, шт</label>
|
||
<input class="form-control bg-body text-body border-secondary" name="quantity" id="biQty" value="1" min="1" required>
|
||
<div class="form-text" id="biQtyHint"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="modal-footer border-secondary">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button type="submit" class="btn btn-outline-accent" {% if not delivery_batches or not deal_items %}disabled{% endif %}>Добавить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="dealItemModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog modal-lg">
|
||
<form method="post" action="{% url 'deal_item_upsert' %}" class="modal-content border-secondary" id="dealItemForm">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="add">
|
||
<input type="hidden" name="deal_id" value="{{ deal.id }}">
|
||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||
<input type="hidden" name="quantity" value="1">
|
||
<input type="hidden" name="entity_id" id="diEntityId" required>
|
||
|
||
<div class="modal-header border-secondary">
|
||
<h5 class="modal-title">Добавить позицию сделки</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||
</div>
|
||
|
||
<div class="modal-body">
|
||
<div class="row g-2 align-items-end">
|
||
<div class="col-md-3">
|
||
<label class="form-label">Тип</label>
|
||
<select class="form-select bg-body text-body border-secondary" id="diType">
|
||
<option value="product">Изделие</option>
|
||
<option value="assembly">Сборочная единица</option>
|
||
<option value="part" selected>Деталь</option>
|
||
</select>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<label class="form-label">Обозначение</label>
|
||
<input class="form-control bg-body text-body border-secondary" id="diDn" autocomplete="off">
|
||
</div>
|
||
<div class="col-md-4">
|
||
<label class="form-label">Наименование</label>
|
||
<input class="form-control bg-body text-body border-secondary" id="diName" autocomplete="off">
|
||
</div>
|
||
<div class="col-md-2 d-grid">
|
||
<button type="button" class="btn btn-outline-secondary" id="diSearchBtn">Поиск</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="small text-muted mt-2" id="diSearchStatus"></div>
|
||
|
||
<div class="mt-2">
|
||
<label class="form-label">Результаты</label>
|
||
<select class="form-select bg-body text-body border-secondary" id="diFound" size="8"></select>
|
||
<div class="form-text">Выбери строку и нажми «Добавить».</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal-footer border-secondary">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button type="submit" class="btn btn-outline-accent">Добавить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="dealItemDeleteModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<form method="post" action="{% url 'deal_item_upsert' %}" class="modal-content border-secondary">
|
||
{% csrf_token %}
|
||
<input type="hidden" name="action" value="delete">
|
||
<input type="hidden" name="deal_id" id="diDelDealId" value="">
|
||
<input type="hidden" name="entity_id" id="diDelEntityId" value="">
|
||
<input type="hidden" name="next" id="diDelNext" value="">
|
||
|
||
<div class="modal-header border-secondary">
|
||
<h5 class="modal-title">Удалить позицию</h5>
|
||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||
</div>
|
||
<div class="modal-body">
|
||
<div class="mb-2">Вы уверены?</div>
|
||
<div class="small text-muted" id="diDelLabel"></div>
|
||
</div>
|
||
<div class="modal-footer border-secondary">
|
||
<button type="button" class="btn btn-outline-secondary" data-bs-dismiss="modal">Отмена</button>
|
||
<button type="submit" class="btn btn-outline-danger">Удалить</button>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<script>
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.querySelectorAll('tr.deal-entity-row[data-href]').forEach(tr => {
|
||
tr.addEventListener('click', (e) => {
|
||
if (e.target && (e.target.closest('button') || e.target.closest('a') || e.target.closest('form') || e.target.closest('input'))) return;
|
||
const url = tr.getAttribute('data-href');
|
||
if (url) window.location.href = url;
|
||
});
|
||
});
|
||
|
||
const spModal = document.getElementById('startProductionModal');
|
||
const spTitle = document.getElementById('spTitle');
|
||
const spSelect = document.getElementById('spBatchItem');
|
||
const spQty = document.getElementById('spQty');
|
||
const spSubmit = document.getElementById('spSubmit');
|
||
|
||
const delModal = document.getElementById('dealItemDeleteModal');
|
||
const delDealId = document.getElementById('diDelDealId');
|
||
const delEntityId = document.getElementById('diDelEntityId');
|
||
const delNext = document.getElementById('diDelNext');
|
||
const delLabel = document.getElementById('diDelLabel');
|
||
|
||
if (delModal) {
|
||
delModal.addEventListener('shown.bs.modal', (event) => {
|
||
const btn = event.relatedTarget;
|
||
const dealId = btn ? (btn.getAttribute('data-deal-id') || '') : '';
|
||
const entityId = btn ? (btn.getAttribute('data-entity-id') || '') : '';
|
||
const nextUrl = btn ? (btn.getAttribute('data-next') || '') : '';
|
||
const label = btn ? (btn.getAttribute('data-entity-label') || '') : '';
|
||
|
||
if (delDealId) delDealId.value = dealId;
|
||
if (delEntityId) delEntityId.value = entityId;
|
||
if (delNext) delNext.value = nextUrl;
|
||
if (delLabel) delLabel.textContent = label;
|
||
});
|
||
}
|
||
|
||
|
||
function spApplyFilter(entityId) {
|
||
if (!spSelect) return;
|
||
let firstVisible = null;
|
||
Array.from(spSelect.options).forEach(opt => {
|
||
const eid = opt.getAttribute('data-entity-id');
|
||
const visible = eid && String(eid) === String(entityId);
|
||
opt.hidden = !visible;
|
||
opt.disabled = !visible;
|
||
if (visible && !firstVisible) firstVisible = opt;
|
||
});
|
||
|
||
if (firstVisible) {
|
||
spSelect.value = firstVisible.value;
|
||
const rem = parseInt(firstVisible.getAttribute('data-rem') || '0', 10) || 0;
|
||
if (spQty) {
|
||
spQty.max = rem > 0 ? String(rem) : '';
|
||
spQty.value = rem > 0 ? String(rem) : '';
|
||
}
|
||
if (spSubmit) spSubmit.disabled = false;
|
||
} else {
|
||
if (spQty) {
|
||
spQty.value = '';
|
||
spQty.removeAttribute('max');
|
||
}
|
||
if (spSubmit) spSubmit.disabled = true;
|
||
}
|
||
}
|
||
|
||
if (spSelect) {
|
||
spSelect.addEventListener('change', () => {
|
||
const opt = spSelect.options[spSelect.selectedIndex];
|
||
const rem = opt ? (parseInt(opt.getAttribute('data-rem') || '0', 10) || 0) : 0;
|
||
if (spQty) {
|
||
spQty.max = rem > 0 ? String(rem) : '';
|
||
spQty.value = rem > 0 ? String(rem) : '';
|
||
}
|
||
});
|
||
}
|
||
|
||
if (spModal) {
|
||
spModal.addEventListener('shown.bs.modal', (event) => {
|
||
const btn = event.relatedTarget;
|
||
const eid = btn ? btn.getAttribute('data-entity-id') : '';
|
||
const label = btn ? btn.getAttribute('data-entity-label') : '';
|
||
if (spTitle) spTitle.textContent = label || '';
|
||
spApplyFilter(eid);
|
||
if (spQty) spQty.focus({ preventScroll: true });
|
||
});
|
||
}
|
||
|
||
});
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const biModal = document.getElementById('dealBatchItemModal');
|
||
const batchSelect = document.getElementById('batchSelect');
|
||
const entitySelect = document.getElementById('biEntitySelect');
|
||
const qtyEl = document.getElementById('biQty');
|
||
const hintEl = document.getElementById('biQtyHint');
|
||
|
||
function syncRemainingHint() {
|
||
if (!entitySelect || !qtyEl) return;
|
||
const opt = entitySelect.options[entitySelect.selectedIndex];
|
||
const rem = opt ? (parseInt(opt.getAttribute('data-rem') || '0', 10) || 0) : 0;
|
||
qtyEl.max = rem > 0 ? String(rem) : '';
|
||
if (rem > 0 && (parseInt(qtyEl.value || '0', 10) || 0) > rem) {
|
||
qtyEl.value = String(rem);
|
||
}
|
||
if (hintEl) {
|
||
hintEl.textContent = rem > 0 ? `Доступно к распределению: ${rem} шт` : 'Доступно к распределению: 0 шт';
|
||
}
|
||
}
|
||
|
||
if (entitySelect) {
|
||
entitySelect.addEventListener('change', syncRemainingHint);
|
||
}
|
||
|
||
document.querySelectorAll('[data-bs-target="#dealBatchItemModal"][data-batch-id]').forEach(btn => {
|
||
btn.addEventListener('click', () => {
|
||
const bid = btn.getAttribute('data-batch-id');
|
||
if (batchSelect && bid) batchSelect.value = String(bid);
|
||
});
|
||
});
|
||
|
||
if (biModal) {
|
||
biModal.addEventListener('shown.bs.modal', () => {
|
||
syncRemainingHint();
|
||
if (entitySelect) entitySelect.focus({ preventScroll: true });
|
||
});
|
||
}
|
||
});
|
||
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
const modalEl = document.getElementById('dealItemModal');
|
||
const formEl = document.getElementById('dealItemForm');
|
||
|
||
const typeEl = document.getElementById('diType');
|
||
const dnEl = document.getElementById('diDn');
|
||
const nameEl = document.getElementById('diName');
|
||
const foundEl = document.getElementById('diFound');
|
||
const idEl = document.getElementById('diEntityId');
|
||
const btn = document.getElementById('diSearchBtn');
|
||
const statusEl = document.getElementById('diSearchStatus');
|
||
|
||
if (!modalEl || !formEl || !typeEl || !dnEl || !nameEl || !foundEl || !idEl || !btn || !statusEl) return;
|
||
|
||
function setStatus(text) {
|
||
statusEl.textContent = text || '';
|
||
}
|
||
|
||
function setSelectedFromFound() {
|
||
idEl.value = foundEl.value || '';
|
||
}
|
||
|
||
async function runSearch() {
|
||
const params = new URLSearchParams({
|
||
entity_type: (typeEl.value || ''),
|
||
q_dn: (dnEl.value || ''),
|
||
q_name: (nameEl.value || ''),
|
||
});
|
||
|
||
setStatus('Поиск...');
|
||
foundEl.innerHTML = '';
|
||
idEl.value = '';
|
||
|
||
let res;
|
||
try {
|
||
res = await fetch('{% url "entities_search" %}?' + params.toString(), { credentials: 'same-origin' });
|
||
} catch (_) {
|
||
setStatus('Ошибка сети при поиске.');
|
||
return;
|
||
}
|
||
|
||
if (!res.ok) {
|
||
setStatus(`Ошибка поиска: ${res.status}`);
|
||
return;
|
||
}
|
||
|
||
let data;
|
||
try {
|
||
data = await res.json();
|
||
} catch (_) {
|
||
setStatus('Ошибка: сервер вернул не JSON.');
|
||
return;
|
||
}
|
||
|
||
if (data && data.error) {
|
||
setStatus(`Ошибка поиска: ${data.error}`);
|
||
return;
|
||
}
|
||
|
||
const items = (data && data.results) || [];
|
||
items.forEach(it => {
|
||
const opt = document.createElement('option');
|
||
opt.value = String(it.id);
|
||
opt.textContent = `${it.type} | ${it.drawing_number || '—'} ${it.name || ''}`;
|
||
foundEl.appendChild(opt);
|
||
});
|
||
|
||
const count = (data && typeof data.count === 'number') ? data.count : items.length;
|
||
|
||
if (items.length) {
|
||
foundEl.value = String(items[0].id);
|
||
setSelectedFromFound();
|
||
setStatus(`Найдено: ${count}`);
|
||
foundEl.focus({ preventScroll: true });
|
||
} else {
|
||
setStatus(`Ничего не найдено (0).`);
|
||
}
|
||
}
|
||
|
||
btn.addEventListener('click', () => runSearch());
|
||
foundEl.addEventListener('change', () => setSelectedFromFound());
|
||
|
||
const onEnterSearch = (e) => {
|
||
if (e.key !== 'Enter') return;
|
||
e.preventDefault();
|
||
runSearch();
|
||
};
|
||
dnEl.addEventListener('keydown', onEnterSearch);
|
||
nameEl.addEventListener('keydown', onEnterSearch);
|
||
|
||
formEl.addEventListener('submit', (e) => {
|
||
setSelectedFromFound();
|
||
if (!idEl.value) {
|
||
e.preventDefault();
|
||
setStatus('Выбери позицию из результатов поиска.');
|
||
}
|
||
});
|
||
|
||
modalEl.addEventListener('shown.bs.modal', () => {
|
||
setStatus('');
|
||
dnEl.focus({ preventScroll: true });
|
||
dnEl.select();
|
||
});
|
||
});
|
||
document.addEventListener('DOMContentLoaded', function () {
|
||
document.querySelectorAll('tr.task-row[data-href]').forEach(function (row) {
|
||
row.addEventListener('click', function (e) {
|
||
if (e.target && (e.target.closest('button') || e.target.closest('.stop-prop'))) return;
|
||
const href = row.getAttribute('data-href');
|
||
if (href) window.location.href = href;
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.sf-progress').forEach(function (el) {
|
||
const done = parseInt(el.getAttribute('data-done-width') || '0', 10) || 0;
|
||
const plan = parseInt(el.getAttribute('data-plan-width') || '0', 10) || 0;
|
||
const doneEl = el.querySelector('.sf-progress-done');
|
||
const planEl = el.querySelector('.sf-progress-plan');
|
||
if (doneEl) doneEl.style.width = `${done}%`;
|
||
if (planEl) planEl.style.width = `${plan}%`;
|
||
});
|
||
|
||
const modal = document.getElementById('workItemModal');
|
||
if (!modal) return;
|
||
|
||
modal.addEventListener('shown.bs.modal', function (event) {
|
||
const btn = event.relatedTarget;
|
||
const entityId = btn.getAttribute('data-entity-id') || '';
|
||
const batchId = btn.getAttribute('data-batch-id') || '';
|
||
const opId = btn.getAttribute('data-operation-id') || '';
|
||
const name = btn.getAttribute('data-task-name') || '';
|
||
const opName = btn.getAttribute('data-operation-name') || '';
|
||
const rem = btn.getAttribute('data-task-rem');
|
||
|
||
document.getElementById('wiEntityId').value = entityId;
|
||
document.getElementById('wiBatchId').value = batchId;
|
||
document.getElementById('wiOperationId').value = opId;
|
||
|
||
document.getElementById('wiTitle').textContent = name;
|
||
document.getElementById('wiOp').textContent = opName ? `Операция: ${opName}` : 'Операция: —';
|
||
|
||
const hint = document.getElementById('wiHint');
|
||
if (hint) hint.textContent = rem !== null ? `Осталось: ${rem} шт` : '';
|
||
|
||
const qty = document.getElementById('wiQty');
|
||
qty.value = '';
|
||
|
||
if (!opId) {
|
||
const submit = document.getElementById('wiSubmit');
|
||
if (submit) submit.disabled = true;
|
||
if (hint) hint.textContent = 'У этой позиции не задан техпроцесс (операции). Добавь операции в паспорте.';
|
||
return;
|
||
}
|
||
|
||
const submit = document.getElementById('wiSubmit');
|
||
if (submit) submit.disabled = false;
|
||
|
||
let remInt = null;
|
||
if (rem && !isNaN(parseInt(rem, 10))) {
|
||
remInt = Math.max(1, parseInt(rem, 10));
|
||
qty.max = remInt;
|
||
qty.value = String(remInt);
|
||
} else {
|
||
qty.removeAttribute('max');
|
||
}
|
||
|
||
qty.focus({ preventScroll: true });
|
||
qty.select();
|
||
|
||
qty.onkeydown = function (e) {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
const form = document.querySelector('#workItemModal form');
|
||
if (form) form.requestSubmit();
|
||
}
|
||
};
|
||
|
||
const radios = Array.from(document.querySelectorAll('input[name="machine_id"]'));
|
||
const noneRadio = document.getElementById('m_none');
|
||
const wsIdEl = document.getElementById('wiWorkshopId');
|
||
const wsLabelEl = document.getElementById('wiWorkshopLabel');
|
||
|
||
const savedMachine = (() => { try { return localStorage.getItem('planning_machine_id'); } catch (_) { return null; } })();
|
||
|
||
const wsId = btn.getAttribute('data-workshop-id') || '';
|
||
const wsName = btn.getAttribute('data-workshop-name') || '';
|
||
|
||
if (wsIdEl) wsIdEl.value = wsId;
|
||
if (wsLabelEl) {
|
||
wsLabelEl.textContent = wsId ? `Цех: ${wsName || '—'}` : 'Цех: —';
|
||
}
|
||
|
||
function setMachineVisible(radio, visible) {
|
||
const lbl = document.querySelector(`label[for="${radio.id}"]`);
|
||
if (lbl) lbl.style.display = visible ? '' : 'none';
|
||
radio.style.display = visible ? '' : 'none';
|
||
radio.disabled = !visible;
|
||
if (!visible && radio.checked) radio.checked = false;
|
||
}
|
||
|
||
radios.forEach(r => {
|
||
if (r.id === 'm_none') {
|
||
setMachineVisible(r, true);
|
||
return;
|
||
}
|
||
const mWs = r.getAttribute('data-workshop-id') || '';
|
||
setMachineVisible(r, !wsId || (mWs && String(mWs) === String(wsId)));
|
||
});
|
||
|
||
let selected = null;
|
||
if (savedMachine) {
|
||
selected = radios.find(r => r.value === savedMachine && !r.disabled);
|
||
}
|
||
|
||
if (selected) {
|
||
selected.checked = true;
|
||
} else if (noneRadio) {
|
||
noneRadio.checked = true;
|
||
}
|
||
|
||
radios.forEach(r => {
|
||
r.onchange = function () {
|
||
if (!r.checked) return;
|
||
if (r.value) {
|
||
try { localStorage.setItem('planning_machine_id', r.value); } catch (_) {}
|
||
} else {
|
||
try { localStorage.removeItem('planning_machine_id'); } catch (_) {}
|
||
}
|
||
};
|
||
});
|
||
});
|
||
});
|
||
|
||
|
||
</script>
|
||
{% endblock %}
|