Конкретно пересмотрел логику работы. Легаси вынесена в архив
All checks were successful
Deploy MES Core / deploy (push) Successful in 13s
All checks were successful
Deploy MES Core / deploy (push) Successful in 13s
This commit is contained in:
141
shiftflow/templates/shiftflow/assembly_closing.html
Normal file
141
shiftflow/templates/shiftflow/assembly_closing.html
Normal file
@@ -0,0 +1,141 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm border-secondary mb-4">
|
||||
<div class="card-header border-secondary d-flex justify-content-between align-items-center py-3">
|
||||
<h3 class="text-accent mb-0">
|
||||
<i class="bi bi-check2-square me-2"></i>Закрытие сборки
|
||||
</h3>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'workitem_detail' workitem.id %}">Назад к заданию</a>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-4">
|
||||
<div class="mb-4">
|
||||
<h5 class="fw-bold">{{ workitem.entity.drawing_number|default:"—" }} {{ workitem.entity.name }}</h5>
|
||||
<div class="text-muted small">Сделка № {{ workitem.deal.number }}</div>
|
||||
<div class="text-muted small">План: {{ workitem.quantity_plan }} шт. · Собрано: {{ workitem.quantity_done }} шт.</div>
|
||||
<div class="text-muted small">Осталось собрать: <strong>{{ remaining }}</strong> шт.</div>
|
||||
{% if to_location %}
|
||||
<div class="text-muted small mt-2">Участок сборки (склад): <strong>{{ to_location.name }}</strong></div>
|
||||
{% else %}
|
||||
<div class="text-danger small mt-2 fw-bold">Участок сборки не определен! Закрытие невозможно.</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="text-muted small mt-2">
|
||||
Пост для отчёта:
|
||||
{% if workitem.machine_id %}
|
||||
<strong>{{ workitem.machine.name }}</strong>
|
||||
{% else %}
|
||||
<strong class="text-warning">не выбран</strong>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if error %}
|
||||
<div class="alert alert-warning border-warning">
|
||||
{{ error }}
|
||||
</div>
|
||||
{% else %}
|
||||
<h6 class="fw-bold border-bottom border-secondary pb-2 mb-3">Наличие компонентов на участке</h6>
|
||||
<div class="table-responsive mb-4">
|
||||
<table class="table table-sm table-hover align-middle">
|
||||
<thead class="table-custom-header">
|
||||
<tr>
|
||||
<th>Компонент</th>
|
||||
<th class="text-center">Нужно на 1 шт</th>
|
||||
<th class="text-center">Есть на участке</th>
|
||||
<th class="text-center">Хватит на сборок</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in components %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold">{{ c.entity.drawing_number|default:"—" }} {{ c.entity.name }}</div>
|
||||
<div class="small text-muted">{{ c.entity.get_entity_type_display }}</div>
|
||||
</td>
|
||||
<td class="text-center">{{ c.req_per_1 }}</td>
|
||||
<td class="text-center">{{ c.available|floatformat:2 }}</td>
|
||||
<td class="text-center fw-bold {% if c.max_possible == 0 %}text-danger{% else %}text-success{% endif %}">
|
||||
{{ c.max_possible }}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="4" class="text-center text-muted">Спецификация пуста или не найдена.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info border-info d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<strong>Максимум можно закрыть сейчас:</strong> {{ max_possible }} шт.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="close">
|
||||
|
||||
<div class="row align-items-end g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted small mb-1">Фактически собрано (шт.)</label>
|
||||
<input type="number" class="form-control border-secondary" name="fact_qty" min="1" max="{{ max_possible }}" value="{{ max_possible }}" {% if max_possible == 0 %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{% if workitem.machine_id %}
|
||||
<button type="submit" class="btn btn-warning w-100" {% if max_possible == 0 %}disabled{% endif %}>
|
||||
Списать компоненты и закрыть сборку
|
||||
</button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-warning w-100" data-bs-toggle="modal" data-bs-target="#selectMachineModal" {% if max_possible == 0 %}disabled{% endif %}>
|
||||
Выбрать пост и закрыть
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="small text-muted mt-2">
|
||||
При закрытии компоненты будут списаны со склада участка <strong>{{ to_location.name }}</strong>, а готовая сборка будет оприходована на этот же участок. Производственный отчёт привязывается к выбранному посту.
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="selectMachineModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<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="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
{% if workshop_machines %}
|
||||
<label class="form-label small text-muted mb-1">Пост</label>
|
||||
<select class="form-select border-secondary" name="machine_id" required>
|
||||
<option value="">— выбрать —</option>
|
||||
{% for m in workshop_machines %}
|
||||
<option value="{{ m.id }}">{{ m.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<div class="alert alert-warning border-warning mb-0">
|
||||
В этом цехе нет постов. Создай пост в «Справочники → Производство → Посты/станки» и привяжи его к этому цеху.
|
||||
</div>
|
||||
{% endif %}
|
||||
</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-warning" {% if not workshop_machines %}disabled{% endif %}>Закрыть</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -48,27 +48,27 @@
|
||||
<th>Дата</th>
|
||||
<th>Сделка</th>
|
||||
<th>Деталь</th>
|
||||
<th>План</th>
|
||||
<th>К закрытию</th>
|
||||
<th data-sort="false">Факт</th>
|
||||
<th data-sort="false">Режим</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for it in items %}
|
||||
{% for wi in workitems %}
|
||||
<tr>
|
||||
<td class="small">{{ it.date|date:"d.m.Y" }}</td>
|
||||
<td><span class="text-accent fw-bold">{{ it.task.deal.number }}</span></td>
|
||||
<td class="fw-bold">{{ it.task.drawing_name }}</td>
|
||||
<td>{{ it.quantity_plan }}</td>
|
||||
<td class="small">{{ wi.date|date:"d.m.Y" }}</td>
|
||||
<td><span class="text-accent fw-bold">{{ wi.deal.number }}</span></td>
|
||||
<td class="fw-bold">{{ wi.entity.drawing_number|default:"—" }} {{ wi.entity.name }}</td>
|
||||
<td>{{ wi.remaining }}</td>
|
||||
<td style="max-width:140px;">
|
||||
<input class="form-control form-control-sm border-secondary" type="number" min="0" max="{{ it.quantity_plan }}" name="fact_{{ it.id }}" id="fact_{{ it.id }}" value="{{ it.quantity_fact }}" {% if not can_edit %}disabled{% endif %}>
|
||||
<input class="form-control form-control-sm border-secondary" type="number" min="0" max="{{ wi.remaining }}" name="fact_{{ wi.id }}" id="fact_{{ wi.id }}" value="0" {% if not can_edit %}disabled{% endif %}>
|
||||
</td>
|
||||
<td style="min-width:260px;">
|
||||
<div class="d-flex gap-2 align-items-center flex-wrap">
|
||||
<button type="button" class="btn btn-sm btn-outline-success closing-set-action" data-item-id="{{ it.id }}" data-action="done" data-plan="{{ it.quantity_plan }}" {% if not can_edit %}disabled{% endif %}>Полностью</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-warning closing-set-action" data-item-id="{{ it.id }}" data-action="partial" data-plan="{{ it.quantity_plan }}" {% if not can_edit %}disabled{% endif %}>Частично</button>
|
||||
<input type="hidden" id="ca_{{ it.id }}" name="close_action_{{ it.id }}" value="">
|
||||
<span class="small text-muted" id="modeLabel_{{ it.id }}"></span>
|
||||
<button type="button" class="btn btn-sm btn-outline-success closing-set-action" data-item-id="{{ wi.id }}" data-action="done" data-plan="{{ wi.remaining }}" {% if not can_edit %}disabled{% endif %}>Полностью</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-warning closing-set-action" data-item-id="{{ wi.id }}" data-action="partial" data-plan="{{ wi.remaining }}" {% if not can_edit %}disabled{% endif %}>Частично</button>
|
||||
<input type="hidden" id="ca_{{ wi.id }}" name="close_action_{{ wi.id }}" value="">
|
||||
<span class="small text-muted" id="modeLabel_{{ wi.id }}"></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
78
shiftflow/templates/shiftflow/closing_workitems.html
Normal file
78
shiftflow/templates/shiftflow/closing_workitems.html
Normal file
@@ -0,0 +1,78 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-3 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<h3 class="text-accent mb-0">
|
||||
<i class="bi bi-check2-square me-2"></i>Закрытие · Мои сменные задания
|
||||
</h3>
|
||||
|
||||
<form class="d-flex flex-wrap gap-2 align-items-end" method="get">
|
||||
<div>
|
||||
<label class="form-label small text-muted mb-1">Поиск</label>
|
||||
<input class="form-control form-control-sm bg-body text-body border-secondary" name="q" value="{{ q }}" placeholder="Сделка / КД / станок / цех">
|
||||
</div>
|
||||
<button class="btn btn-outline-accent btn-sm" type="submit">
|
||||
<i class="bi bi-search me-1"></i>Показать
|
||||
</button>
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'closing_workitems' %}">
|
||||
<i class="bi bi-arrow-counterclockwise me-1"></i>Сброс
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th style="width:110px;">Сделка</th>
|
||||
<th>КД</th>
|
||||
<th class="text-center" style="width:180px;">Операция</th>
|
||||
<th class="text-center" style="width:160px;">Цех/Пост</th>
|
||||
<th class="text-center" style="width:90px;">План</th>
|
||||
<th class="text-center" style="width:90px;">Факт</th>
|
||||
<th class="text-center" style="width:110px;">Остаток</th>
|
||||
<th class="text-center" style="width:120px;">Действие</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for wi in workitems %}
|
||||
<tr>
|
||||
<td class="fw-bold">
|
||||
<a class="text-decoration-none" href="{% url 'planning_deal' wi.deal.id %}">{{ wi.deal.number }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<div class="fw-bold">
|
||||
<a class="text-decoration-none text-reset" href="{% url 'workitem_detail' wi.id %}">
|
||||
{{ wi.entity.drawing_number|default:"—" }} {{ wi.entity.name }}
|
||||
</a>
|
||||
</div>
|
||||
<div class="small text-muted">{{ wi.entity.get_entity_type_display }}</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if wi.operation %}{{ wi.operation.name }}{% else %}{{ wi.stage|default:"—" }}{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if wi.machine %}{{ wi.machine.name }}{% elif wi.workshop %}{{ wi.workshop.name }}{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="text-center">{{ wi.quantity_plan }}</td>
|
||||
<td class="text-center">{{ wi.quantity_done }}</td>
|
||||
<td class="text-center fw-bold {% if wi.remaining > 0 %}text-warning{% else %}text-success{% endif %}">
|
||||
{{ wi.remaining }}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a class="btn btn-outline-warning btn-sm" href="{{ wi.close_url }}">
|
||||
Закрыть
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="8" class="text-center text-muted py-4">Нет активных сменных заданий.</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -86,6 +86,10 @@
|
||||
<option value="done">Завершена</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-muted">Срок отгрузки</label>
|
||||
<input type="date" class="form-control border-secondary" id="dealDueDate">
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<label class="form-label small text-muted">Описание</label>
|
||||
<textarea class="form-control border-secondary" rows="3" id="dealDescription"></textarea>
|
||||
@@ -112,6 +116,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const dealNumber = document.getElementById('dealNumber');
|
||||
const dealStatus = document.getElementById('dealStatus');
|
||||
const dealDescription = document.getElementById('dealDescription');
|
||||
const dealDueDate = document.getElementById('dealDueDate');
|
||||
const dealSaveBtn = document.getElementById('dealSaveBtn');
|
||||
|
||||
function getCookie(name) {
|
||||
@@ -139,6 +144,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
dealModal.addEventListener('show.bs.modal', function () {
|
||||
if (dealNumber) dealNumber.value = '';
|
||||
if (dealDescription) dealDescription.value = '';
|
||||
if (dealDueDate) dealDueDate.value = '';
|
||||
if (dealStatus) dealStatus.value = 'work';
|
||||
});
|
||||
}
|
||||
@@ -150,6 +156,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
status: dealStatus ? dealStatus.value : 'work',
|
||||
company_id: '{{ company.id }}',
|
||||
description: (dealDescription ? dealDescription.value : ''),
|
||||
due_date: dealDueDate ? dealDueDate.value : '',
|
||||
};
|
||||
await postForm('{% url "deal_upsert" %}', payload);
|
||||
window.location.reload();
|
||||
|
||||
36
shiftflow/templates/shiftflow/directories.html
Normal file
36
shiftflow/templates/shiftflow/directories.html
Normal file
@@ -0,0 +1,36 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary">
|
||||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-journals me-2"></i>Справочники</h3>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<a class="btn btn-outline-accent" href="{% url 'products' %}">
|
||||
<i class="bi bi-diagram-3 me-2"></i>Номенклатура изделий
|
||||
</a>
|
||||
|
||||
<a class="btn btn-outline-accent" href="{% url 'supply_catalog' %}">
|
||||
<i class="bi bi-box-seam me-2"></i>Номенклатура снабжения (покупное/аутсорс)
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="btn-group" role="group" aria-label="Материалы">
|
||||
<a class="btn btn-outline-accent" href="{% url 'materials_catalog' %}">Материалы</a>
|
||||
<a class="btn btn-outline-accent" href="{% url 'material_categories_catalog' %}">Категории материалов</a>
|
||||
<a class="btn btn-outline-accent" href="{% url 'steel_grades_catalog' %}">Марки стали</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="btn-group" role="group" aria-label="Производство">
|
||||
<a class="btn btn-outline-accent" href="{% url 'locations_catalog' %}">Склады</a>
|
||||
<a class="btn btn-outline-accent" href="{% url 'workshops_catalog' %}">Цеха</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
291
shiftflow/templates/shiftflow/legacy_closing.html
Normal file
291
shiftflow/templates/shiftflow/legacy_closing.html
Normal file
@@ -0,0 +1,291 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card border-secondary mb-3 shadow-sm">
|
||||
<div class="card-body py-2">
|
||||
<form method="get" class="row g-2 align-items-center">
|
||||
<div class="col-md-4">
|
||||
<label class="small text-muted mb-1 fw-bold">Станок:</label>
|
||||
<select class="form-select form-select-sm bg-body text-body border-secondary" name="machine_id" onchange="this.form.submit()">
|
||||
<option value="">— выбрать —</option>
|
||||
{% for m in machines %}
|
||||
<option value="{{ m.id }}" {% if selected_machine_id == m.id|stringformat:"s" %}selected{% endif %}>{{ m.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="small text-muted mb-1 fw-bold">Материал:</label>
|
||||
<select class="form-select form-select-sm bg-body text-body border-secondary" name="material_id" onchange="this.form.submit()">
|
||||
<option value="">— выбрать —</option>
|
||||
{% for mat in materials %}
|
||||
<option value="{{ mat.id }}" {% if selected_material_id == mat.id|stringformat:"s" %}selected{% endif %}>{{ mat.full_name|default:mat.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2 text-end mt-auto">
|
||||
<a class="btn btn-outline-secondary btn-sm w-100" href="{% url 'legacy_closing' %}">Сброс</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="machine_id" value="{{ selected_machine_id }}">
|
||||
<input type="hidden" name="material_id" value="{{ selected_material_id }}">
|
||||
|
||||
<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-0"><i class="bi bi-archive me-2"></i>Архив / Закрытие</h3>
|
||||
<div class="small text-muted">Legacy: Item</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Дата</th>
|
||||
<th>Сделка</th>
|
||||
<th>Деталь</th>
|
||||
<th>План</th>
|
||||
<th data-sort="false">Факт</th>
|
||||
<th data-sort="false">Режим</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for it in items %}
|
||||
<tr>
|
||||
<td class="small">{{ it.date|date:"d.m.Y" }}</td>
|
||||
<td><span class="text-accent fw-bold">{{ it.task.deal.number }}</span></td>
|
||||
<td class="fw-bold">{{ it.task.drawing_name }}</td>
|
||||
<td>{{ it.quantity_plan }}</td>
|
||||
<td style="max-width:140px;">
|
||||
<input class="form-control form-control-sm border-secondary" type="number" min="0" max="{{ it.quantity_plan }}" name="fact_{{ it.id }}" id="fact_{{ it.id }}" value="{{ it.quantity_fact }}" {% if not can_edit %}disabled{% endif %}>
|
||||
</td>
|
||||
<td style="min-width:260px;">
|
||||
<div class="d-flex gap-2 align-items-center flex-wrap">
|
||||
<button type="button" class="btn btn-sm btn-outline-success closing-set-action" data-item-id="{{ it.id }}" data-action="done" data-plan="{{ it.quantity_plan }}" {% if not can_edit %}disabled{% endif %}>Полностью</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-warning closing-set-action" data-item-id="{{ it.id }}" data-action="partial" data-plan="{{ it.quantity_plan }}" {% if not can_edit %}disabled{% endif %}>Частично</button>
|
||||
<input type="hidden" id="ca_{{ it.id }}" name="close_action_{{ it.id }}" value="">
|
||||
<span class="small text-muted" id="modeLabel_{{ it.id }}"></span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6" class="text-center text-muted py-4">Выбери станок и материал</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-3">
|
||||
<h5 class="mb-0">Списание со склада цеха (единицы)</h5>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Поступление</th>
|
||||
<th>Сделка</th>
|
||||
<th>Единица</th>
|
||||
<th>Размеры</th>
|
||||
<th>Доступно</th>
|
||||
<th data-sort="false">Использовано</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in stock_items %}
|
||||
<tr>
|
||||
<td class="small">{% if s.created_at %}{{ s.created_at|date:"d.m.Y H:i" }}{% endif %}</td>
|
||||
<td>
|
||||
{% if s.deal_id %}
|
||||
<span class="text-accent fw-bold">{{ s.deal.number }}</span>
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ s }}</td>
|
||||
<td>
|
||||
{% if s.current_length and s.current_width %}
|
||||
{{ s.current_length|floatformat:"-g" }} × {{ s.current_width|floatformat:"-g" }} мм
|
||||
{% elif s.current_length %}
|
||||
{{ s.current_length|floatformat:"-g" }} мм
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ s.quantity }}</td>
|
||||
<td style="max-width:140px;">
|
||||
<input class="form-control form-control-sm border-secondary" name="consume_{{ s.id }}" placeholder="0" {% if not can_edit %}disabled{% endif %}>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5" class="text-center text-muted py-4">Нет единиц на складе для выбранного материала</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow border-secondary">
|
||||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">Остаток ДО</h5>
|
||||
<button type="button" class="btn btn-outline-accent btn-sm" id="addRemnantBtn" {% if not can_edit %}disabled{% endif %}>Добавить ДО</button>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Кол-во</th>
|
||||
<th>Длина (мм)</th>
|
||||
<th>Ширина (мм)</th>
|
||||
<th data-sort="false"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="remnantBody">
|
||||
<tr id="remnantEmptyRow">
|
||||
<td colspan="4" class="text-center text-muted py-4">ДО не добавлены</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex justify-content-end mt-3">
|
||||
<button type="submit" class="btn btn-outline-accent" {% if not can_edit %}disabled{% endif %}>Сохранить</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const canEdit = {% if can_edit %}true{% else %}false{% endif %};
|
||||
|
||||
document.querySelectorAll('.closing-set-action').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
if (!canEdit) return;
|
||||
|
||||
const itemId = btn.getAttribute('data-item-id');
|
||||
const action = btn.getAttribute('data-action');
|
||||
const plan = parseInt(btn.getAttribute('data-plan') || '0', 10) || 0;
|
||||
|
||||
const hidden = document.getElementById('ca_' + itemId);
|
||||
const fact = document.getElementById('fact_' + itemId);
|
||||
const label = document.getElementById('modeLabel_' + itemId);
|
||||
|
||||
if (hidden) hidden.value = action;
|
||||
|
||||
const cell = btn.closest('td');
|
||||
if (cell) {
|
||||
cell.querySelectorAll('.closing-set-action').forEach(b => {
|
||||
const a = b.getAttribute('data-action');
|
||||
if (a === 'done') {
|
||||
b.classList.remove('btn-success');
|
||||
b.classList.add('btn-outline-success');
|
||||
}
|
||||
if (a === 'partial') {
|
||||
b.classList.remove('btn-warning');
|
||||
b.classList.add('btn-outline-warning');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (action === 'done') {
|
||||
btn.classList.remove('btn-outline-success');
|
||||
btn.classList.add('btn-success');
|
||||
if (fact) {
|
||||
fact.value = String(plan);
|
||||
fact.readOnly = true;
|
||||
}
|
||||
if (label) label.textContent = 'Выбрано: полностью';
|
||||
}
|
||||
|
||||
if (action === 'partial') {
|
||||
btn.classList.remove('btn-outline-warning');
|
||||
btn.classList.add('btn-warning');
|
||||
if (fact) {
|
||||
fact.readOnly = false;
|
||||
fact.focus();
|
||||
fact.select();
|
||||
}
|
||||
if (label) label.textContent = 'Выбрано: частично';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const addBtn = document.getElementById('addRemnantBtn');
|
||||
const body = document.getElementById('remnantBody');
|
||||
const emptyRow = document.getElementById('remnantEmptyRow');
|
||||
|
||||
function renumberRemnants() {
|
||||
const rows = Array.from(body.querySelectorAll('tr[data-remnant-row="1"]'));
|
||||
rows.forEach((tr, idx) => {
|
||||
const qty = tr.querySelector('input[data-field="qty"]');
|
||||
const len = tr.querySelector('input[data-field="len"]');
|
||||
const wid = tr.querySelector('input[data-field="wid"]');
|
||||
if (qty) qty.name = 'remnant_qty_' + idx;
|
||||
if (len) len.name = 'remnant_len_' + idx;
|
||||
if (wid) wid.name = 'remnant_wid_' + idx;
|
||||
});
|
||||
|
||||
if (emptyRow) {
|
||||
emptyRow.style.display = rows.length ? 'none' : '';
|
||||
}
|
||||
}
|
||||
|
||||
function addRemnantRow() {
|
||||
if (!canEdit) return;
|
||||
|
||||
const rows = Array.from(body.querySelectorAll('tr[data-remnant-row="1"]'));
|
||||
if (rows.length >= 50) return;
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
tr.setAttribute('data-remnant-row', '1');
|
||||
|
||||
tr.innerHTML = `
|
||||
<td style="max-width:180px;">
|
||||
<input class="form-control form-control-sm border-secondary" data-field="qty" inputmode="decimal" placeholder="Кол-во" required>
|
||||
</td>
|
||||
<td style="max-width:180px;">
|
||||
<input class="form-control form-control-sm border-secondary" data-field="len" inputmode="decimal" placeholder="Длина (мм)">
|
||||
</td>
|
||||
<td style="max-width:180px;">
|
||||
<input class="form-control form-control-sm border-secondary" data-field="wid" inputmode="decimal" placeholder="Ширина (мм)">
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" data-action="remove">Удалить</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
const rm = tr.querySelector('button[data-action="remove"]');
|
||||
if (rm) {
|
||||
rm.addEventListener('click', () => {
|
||||
tr.remove();
|
||||
renumberRemnants();
|
||||
});
|
||||
}
|
||||
|
||||
body.appendChild(tr);
|
||||
renumberRemnants();
|
||||
|
||||
const first = tr.querySelector('input[data-field="qty"]');
|
||||
if (first) {
|
||||
first.focus();
|
||||
first.select();
|
||||
}
|
||||
}
|
||||
|
||||
if (addBtn) {
|
||||
addBtn.addEventListener('click', addRemnantRow);
|
||||
}
|
||||
|
||||
renumberRemnants();
|
||||
});
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
21
shiftflow/templates/shiftflow/legacy_registry.html
Normal file
21
shiftflow/templates/shiftflow/legacy_registry.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
{% include 'shiftflow/partials/_filter.html' %}
|
||||
|
||||
<div class="card shadow border-secondary">
|
||||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-archive me-2"></i>Архив / Реестр</h3>
|
||||
<div class="small text-muted">Legacy: Item</div>
|
||||
</div>
|
||||
{% if user_role in 'admin,technologist,master' %}
|
||||
<a class="btn btn-outline-secondary btn-sm" target="_blank" href="{% url 'registry_print' %}?{{ request.GET.urlencode }}">
|
||||
<i class="bi bi-printer me-1"></i>Печать
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include 'shiftflow/partials/_items_table.html' with items=items %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
173
shiftflow/templates/shiftflow/legacy_writeoffs.html
Normal file
173
shiftflow/templates/shiftflow/legacy_writeoffs.html
Normal file
@@ -0,0 +1,173 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card border-secondary mb-3 shadow-sm">
|
||||
<div class="card-body py-2">
|
||||
<form method="get" class="row g-2 align-items-end">
|
||||
<div class="col-md-auto">
|
||||
<label class="small text-muted mb-1 fw-bold">Период (с):</label>
|
||||
<input type="date" name="start_date" class="form-control form-control-sm bg-body text-body border-secondary" value="{{ start_date }}">
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<label class="small text-muted mb-1 fw-bold">Период (по):</label>
|
||||
<input type="date" name="end_date" class="form-control form-control-sm bg-body text-body border-secondary" value="{{ end_date }}">
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<button type="submit" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-funnel me-1"></i>Показать
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-md-auto">
|
||||
<a href="{% url 'legacy_writeoffs' %}?reset=1" class="btn btn-outline-secondary btn-sm">
|
||||
<i class="bi bi-arrow-counterclockwise me-1"></i>Сброс
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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-0"><i class="bi bi-archive me-2"></i>Архив / Списание / Производство</h3>
|
||||
<div class="small text-muted">По производственным отчетам</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
{% for card in report_cards %}
|
||||
<div class="border border-secondary rounded p-3 mb-3">
|
||||
<div class="d-flex flex-wrap justify-content-between gap-2">
|
||||
<div class="fw-bold">
|
||||
{{ card.report.date|date:"d.m.Y" }} — {{ card.report.machine }} — {{ card.report.operator }}
|
||||
<span class="text-muted small ms-2">#{{ card.report.id }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mt-1">
|
||||
<div class="col-lg-4">
|
||||
<div class="small text-muted fw-bold mb-1">Списано</div>
|
||||
{% if card.report.consumptions.all %}
|
||||
<ul class="mb-0">
|
||||
{% for c in card.report.consumptions.all %}
|
||||
{% if c.stock_item_id and c.stock_item.material_id %}
|
||||
<li>
|
||||
{{ c.stock_item.material.full_name|default:c.stock_item.material.name }}
|
||||
({% if c.stock_item.current_length and c.stock_item.current_width %}{{ c.stock_item.current_length|floatformat:"-g" }}×{{ c.stock_item.current_width|floatformat:"-g" }}{% elif c.stock_item.current_length %}{{ c.stock_item.current_length|floatformat:"-g" }}{% else %}—{% endif %})
|
||||
{{ c.quantity|floatformat:"-g" }} шт
|
||||
</li>
|
||||
{% elif c.material_id %}
|
||||
<li>{{ c.material }} {{ c.quantity|floatformat:"-g" }} шт</li>
|
||||
{% else %}
|
||||
<li>— {{ c.quantity|floatformat:"-g" }} шт</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="text-muted small">—</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="small text-muted fw-bold mb-1">Произведено</div>
|
||||
{% if card.produced %}
|
||||
<ul class="mb-0">
|
||||
{% for k,v in card.produced.items %}
|
||||
<li>{{ k }}: {{ v }} шт</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="text-muted small">—</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="small text-muted fw-bold mb-1">Остаток ДО</div>
|
||||
{% if card.report.remnants.all %}
|
||||
<ul class="mb-0">
|
||||
{% for r in card.report.remnants.all %}
|
||||
<li>
|
||||
{{ r.material.full_name|default:r.material.name|default:r.material }}
|
||||
({% if r.current_length and r.current_width %}{{ r.current_length|floatformat:"-g" }}×{{ r.current_width|floatformat:"-g" }}{% elif r.current_length %}{{ r.current_length|floatformat:"-g" }}{% else %}—{% endif %})
|
||||
{{ r.quantity|floatformat:"-g" }} шт
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
<div class="text-muted small">—</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="text-muted">За выбранный период отчётов нет.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow border-secondary">
|
||||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-check2-square me-2"></i>Сменные задания (1С)</h3>
|
||||
<div class="small text-muted">Отметка «Списано в 1С»</div>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mb-0">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="start_date" value="{{ start_date }}">
|
||||
<input type="hidden" name="end_date" value="{{ end_date }}">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th data-sort="false"></th>
|
||||
<th>Дата</th>
|
||||
<th>Сделка</th>
|
||||
<th>Станок</th>
|
||||
<th>Позиция</th>
|
||||
<th>План / Факт</th>
|
||||
<th data-sort="false" class="text-center">1С</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for it in items %}
|
||||
<tr>
|
||||
<td style="width:40px;">
|
||||
{% if can_edit %}
|
||||
<input type="checkbox" class="form-check-input" name="item_ids" value="{{ it.id }}">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="small">{{ it.date|date:"d.m.Y" }}</td>
|
||||
<td><span class="text-accent fw-bold">{{ it.task.deal.number|default:"-" }}</span></td>
|
||||
<td><span class="badge bg-dark border border-secondary">{{ it.machine.name }}</span></td>
|
||||
<td class="fw-bold">{{ it.task.drawing_name|default:"—" }}</td>
|
||||
<td>
|
||||
<span class="text-info fw-bold">{{ it.quantity_plan }}</span> /
|
||||
<span class="text-success">{{ it.quantity_fact }}</span>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if it.is_synced_1c %}
|
||||
<i class="bi bi-check-circle-fill text-success" title="Учтено"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-clock-history text-muted" title="Ожидает"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="7" class="text-center text-muted py-4">Нет сменных заданий за период</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% if can_edit %}
|
||||
<div class="card-body border-top border-secondary d-flex justify-content-end">
|
||||
<button type="submit" class="btn btn-outline-accent">
|
||||
<i class="bi bi-save me-2"></i>Сохранить
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
72
shiftflow/templates/shiftflow/locations_catalog.html
Normal file
72
shiftflow/templates/shiftflow/locations_catalog.html
Normal file
@@ -0,0 +1,72 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-3 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-boxes me-2"></i>Справочник · Склады</h3>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'directories' %}">Назад</a>
|
||||
</div>
|
||||
|
||||
{% if can_edit %}
|
||||
<div class="card-body border-bottom border-secondary">
|
||||
<form method="post" class="row g-2 align-items-end">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="create">
|
||||
|
||||
<div class="col-md-7">
|
||||
<label class="form-label small text-muted mb-1">Название склада</label>
|
||||
<input class="form-control border-secondary" name="name" placeholder="Напр: Центральный склад" required>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-outline-accent w-100" type="submit">Создать</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle" data-sortable="1">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th class="text-center" style="width:80px;" data-sort-type="number">№</th>
|
||||
<th>Склад</th>
|
||||
<th class="text-center" style="width:140px;">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for l in locations %}
|
||||
<tr>
|
||||
<td class="text-center">{{ forloop.counter }}</td>
|
||||
|
||||
<td class="fw-bold">
|
||||
{% if can_edit %}
|
||||
<form method="post" class="row g-2 align-items-center">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="update">
|
||||
<input type="hidden" name="location_id" value="{{ l.id }}">
|
||||
<div class="col-12">
|
||||
<input class="form-control form-control-sm border-secondary" name="name" value="{{ l.name }}">
|
||||
</div>
|
||||
{% else %}
|
||||
{{ l.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td class="text-center">
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-outline-accent btn-sm" type="submit">Сохранить</button>
|
||||
</form>
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3" class="text-center text-muted py-4">Складов нет.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
158
shiftflow/templates/shiftflow/machines_catalog.html
Normal file
158
shiftflow/templates/shiftflow/machines_catalog.html
Normal file
@@ -0,0 +1,158 @@
|
||||
{% 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-0"><i class="bi bi-building me-2"></i>{{ workshop.name }}</h3>
|
||||
<div class="small text-muted">Цех · ID {{ workshop.id }}</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'workshops_catalog' %}">Назад</a>
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-outline-accent btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#addMachineModal">
|
||||
<i class="bi bi-plus-circle me-1"></i>Добавить пост
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body border-bottom border-secondary">
|
||||
{% if can_edit %}
|
||||
<form method="post" class="row g-2 align-items-end">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="update_workshop">
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small text-muted mb-1">Наименование цеха</label>
|
||||
<input class="form-control border-secondary" name="name" value="{{ workshop.name }}">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label small text-muted mb-1">Склад цеха</label>
|
||||
<select class="form-select border-secondary" name="location_id">
|
||||
<option value="">— не задан —</option>
|
||||
{% for l in locations %}
|
||||
<option value="{{ l.id }}" {% if workshop.location_id == l.id %}selected{% endif %}>{{ l.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-2">
|
||||
<button class="btn btn-outline-accent w-100" type="submit">Сохранить</button>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<div class="small text-muted">Цех</div>
|
||||
<div class="fw-bold">{{ workshop.name }}</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="small text-muted">Склад цеха</div>
|
||||
<div class="fw-bold">{{ workshop.location.name|default:"—" }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle" data-sortable="1">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th class="text-center" style="width:80px;" data-sort-type="number">№</th>
|
||||
<th class="text-center" style="width:90px;" data-sort-type="number">ID</th>
|
||||
<th>Пост/станок</th>
|
||||
<th class="text-center" style="width:200px;">Тип</th>
|
||||
<th class="text-center" style="width:220px;">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for m in machines %}
|
||||
<tr>
|
||||
<td class="text-center">{{ forloop.counter }}</td>
|
||||
<td class="text-center text-muted">{{ m.id }}</td>
|
||||
|
||||
<td class="fw-bold">
|
||||
{% if can_edit %}
|
||||
<form method="post" class="row g-2 align-items-center">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="update_machine">
|
||||
<input type="hidden" name="machine_id" value="{{ m.id }}">
|
||||
<div class="col-12">
|
||||
<input class="form-control form-control-sm border-secondary" name="name" value="{{ m.name }}">
|
||||
</div>
|
||||
{% else %}
|
||||
{{ m.name }}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td class="text-center">
|
||||
{% if can_edit %}
|
||||
<select class="form-select form-select-sm border-secondary" name="machine_type">
|
||||
{% for k, v in machine_types %}
|
||||
<option value="{{ k }}" {% if m.machine_type == k %}selected{% endif %}>{{ v }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
{{ m.get_machine_type_display }}
|
||||
{% endif %}
|
||||
</td>
|
||||
|
||||
<td class="text-center">
|
||||
{% if can_edit %}
|
||||
<div class="d-flex justify-content-center gap-2">
|
||||
<button class="btn btn-outline-accent btn-sm" type="submit">Сохранить</button>
|
||||
</form>
|
||||
<form method="post" class="m-0">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="delete_machine">
|
||||
<input type="hidden" name="machine_id" value="{{ m.id }}">
|
||||
<button class="btn btn-outline-secondary btn-sm" type="submit">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5" class="text-center text-muted py-4">Постов нет.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if can_edit %}
|
||||
<div class="modal fade" id="addMachineModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content border-secondary">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="create_machine">
|
||||
<div class="modal-header border-secondary">
|
||||
<h5 class="modal-title">Добавить пост</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<label class="form-label small text-muted mb-1">Название</label>
|
||||
<input class="form-control border-secondary mb-3" name="name" placeholder="Напр: Сварка-1" required>
|
||||
|
||||
<label class="form-label small text-muted mb-1">Тип</label>
|
||||
<select class="form-select border-secondary" name="machine_type">
|
||||
{% for k, v in machine_types %}
|
||||
<option value="{{ k }}">{{ v }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</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>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
149
shiftflow/templates/shiftflow/material_categories_catalog.html
Normal file
149
shiftflow/templates/shiftflow/material_categories_catalog.html
Normal file
@@ -0,0 +1,149 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-3 d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb" class="small">
|
||||
<ol class="breadcrumb mb-1">
|
||||
<li class="breadcrumb-item"><a class="text-decoration-none" href="{% url 'directories' %}">Справочники</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Категории материалов</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-tags me-2"></i>Категории материалов</h3>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<form method="get" class="d-flex gap-2 align-items-center">
|
||||
<input class="form-control form-control-sm bg-body text-body border-secondary" name="q" value="{{ q }}" placeholder="Поиск...">
|
||||
<button class="btn btn-outline-accent btn-sm" type="submit"><i class="bi bi-search me-1"></i>Найти</button>
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'material_categories_catalog' %}"><i class="bi bi-arrow-counterclockwise me-1"></i>Сброс</a>
|
||||
</form>
|
||||
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'directories' %}"><i class="bi bi-arrow-left me-1"></i>Назад</a>
|
||||
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-outline-accent btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#catModal" onclick="openCatCreate()">
|
||||
<i class="bi bi-plus-lg me-1"></i>Создать
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Название</th>
|
||||
<th>ГОСТ</th>
|
||||
<th>Форма</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for c in categories %}
|
||||
<tr role="button" {% if can_edit %}onclick="openCatEdit(this)"{% endif %}
|
||||
data-id="{{ c.id }}" data-name="{{ c.name }}" data-gost="{{ c.gost_standard }}" data-form="{{ c.form_factor }}">
|
||||
<td class="fw-bold">{{ c.name }}</td>
|
||||
<td>{{ c.gost_standard|default:"—" }}</td>
|
||||
<td class="small text-muted">{{ c.get_form_factor_display }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="3" class="text-center text-muted py-4">Нет данных</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" id="catModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<form class="modal-content border-secondary" onsubmit="event.preventDefault(); saveCat();">
|
||||
<div class="modal-header border-secondary">
|
||||
<h5 class="modal-title" id="catModalTitle">Категория</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" id="catId">
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-5">
|
||||
<label class="form-label">Название</label>
|
||||
<input class="form-control bg-body text-body border-secondary" id="catName" required {% if not can_edit %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">ГОСТ</label>
|
||||
<input class="form-control bg-body text-body border-secondary" id="catGost" {% if not can_edit %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Форма</label>
|
||||
<select class="form-select bg-body text-body border-secondary" id="catForm" {% if not can_edit %}disabled{% endif %}>
|
||||
<option value="sheet">Лист</option>
|
||||
<option value="bar">Прокат/хлыст</option>
|
||||
<option value="other">Прочее</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer border-secondary">
|
||||
<button type="button" class="btn btn-outline-accent" data-bs-dismiss="modal">Отмена</button>
|
||||
{% if can_edit %}
|
||||
<button type="submit" class="btn btn-outline-accent">Сохранить</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
return '';
|
||||
}
|
||||
|
||||
function openCatCreate() {
|
||||
document.getElementById('catModalTitle').textContent = 'Категория (создание)';
|
||||
document.getElementById('catId').value = '';
|
||||
document.getElementById('catName').value = '';
|
||||
document.getElementById('catGost').value = '';
|
||||
document.getElementById('catForm').value = 'other';
|
||||
new bootstrap.Modal(document.getElementById('catModal')).show();
|
||||
}
|
||||
|
||||
function openCatEdit(tr) {
|
||||
document.getElementById('catModalTitle').textContent = 'Категория (правка)';
|
||||
document.getElementById('catId').value = tr.getAttribute('data-id') || '';
|
||||
document.getElementById('catName').value = tr.getAttribute('data-name') || '';
|
||||
document.getElementById('catGost').value = tr.getAttribute('data-gost') || '';
|
||||
document.getElementById('catForm').value = tr.getAttribute('data-form') || 'other';
|
||||
new bootstrap.Modal(document.getElementById('catModal')).show();
|
||||
}
|
||||
|
||||
async function saveCat() {
|
||||
const fd = new FormData();
|
||||
fd.append('id', document.getElementById('catId').value);
|
||||
fd.append('name', document.getElementById('catName').value);
|
||||
fd.append('gost_standard', document.getElementById('catGost').value);
|
||||
fd.append('form_factor', document.getElementById('catForm').value);
|
||||
|
||||
const res = await fetch("{% url 'material_category_upsert' %}", {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-CSRFToken': getCookie('csrftoken') },
|
||||
body: fd,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
alert('Не удалось сохранить категорию');
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
179
shiftflow/templates/shiftflow/materials_catalog.html
Normal file
179
shiftflow/templates/shiftflow/materials_catalog.html
Normal file
@@ -0,0 +1,179 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-3 d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb" class="small">
|
||||
<ol class="breadcrumb mb-1">
|
||||
<li class="breadcrumb-item"><a class="text-decoration-none" href="{% url 'directories' %}">Справочники</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Материалы</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-box-seam me-2"></i>Материалы</h3>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<form method="get" class="d-flex gap-2 align-items-center">
|
||||
<input class="form-control form-control-sm bg-body text-body border-secondary" name="q" value="{{ q }}" placeholder="Поиск...">
|
||||
<button class="btn btn-outline-accent btn-sm" type="submit"><i class="bi bi-search me-1"></i>Найти</button>
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'materials_catalog' %}"><i class="bi bi-arrow-counterclockwise me-1"></i>Сброс</a>
|
||||
</form>
|
||||
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'directories' %}"><i class="bi bi-arrow-left me-1"></i>Назад</a>
|
||||
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-outline-accent btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#materialModal" onclick="openMaterialCreate()">
|
||||
<i class="bi bi-plus-lg me-1"></i>Создать
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Категория</th>
|
||||
<th>Марка</th>
|
||||
<th>Наименование</th>
|
||||
<th>Полное имя</th>
|
||||
<th>Масса</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr role="button" {% if can_edit %}onclick="openMaterialEdit({{ r.m.id }})"{% endif %}>
|
||||
<td>{{ r.m.category.name }}</td>
|
||||
<td>{{ r.m.steel_grade.name|default:"—" }}</td>
|
||||
<td class="fw-bold">{{ r.m.name }}</td>
|
||||
<td class="small text-muted">{{ r.m.full_name }}</td>
|
||||
<td>
|
||||
{% if r.m.mass_per_unit %}
|
||||
{{ r.m.mass_per_unit|floatformat:"-g" }} {{ r.unit }}
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5" class="text-center text-muted py-4">Нет данных</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="modal fade" id="materialModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<form class="modal-content border-secondary" onsubmit="event.preventDefault(); saveMaterial();">
|
||||
<div class="modal-header border-secondary">
|
||||
<h5 class="modal-title" id="materialModalTitle">Материал</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" id="materialId">
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Категория</label>
|
||||
<select class="form-select bg-body text-body border-secondary" id="materialCategory" required {% if not can_edit %}disabled{% endif %}>
|
||||
<option value="">— выбрать —</option>
|
||||
{% for c in categories %}
|
||||
<option value="{{ c.id }}">{{ c.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Марка стали</label>
|
||||
<select class="form-select bg-body text-body border-secondary" id="materialGrade" {% if not can_edit %}disabled{% endif %}>
|
||||
<option value="">— не выбрана —</option>
|
||||
{% for g in grades %}
|
||||
<option value="{{ g.id }}">{{ g.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-8">
|
||||
<label class="form-label">Наименование</label>
|
||||
<input class="form-control bg-body text-body border-secondary" id="materialName" required {% if not can_edit %}disabled{% endif %}>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Масса на ед. учёта</label>
|
||||
<input class="form-control bg-body text-body border-secondary" id="materialMassPerUnit" inputmode="decimal" placeholder="Напр. 78.5" {% if not can_edit %}disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer border-secondary">
|
||||
<button type="button" class="btn btn-outline-accent" data-bs-dismiss="modal">Отмена</button>
|
||||
{% if can_edit %}
|
||||
<button type="submit" class="btn btn-outline-accent">Сохранить</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
return '';
|
||||
}
|
||||
|
||||
function openMaterialCreate() {
|
||||
document.getElementById('materialModalTitle').textContent = 'Материал (создание)';
|
||||
document.getElementById('materialId').value = '';
|
||||
document.getElementById('materialCategory').value = '';
|
||||
document.getElementById('materialGrade').value = '';
|
||||
document.getElementById('materialName').value = '';
|
||||
document.getElementById('materialMassPerUnit').value = '';
|
||||
new bootstrap.Modal(document.getElementById('materialModal')).show();
|
||||
}
|
||||
|
||||
async function openMaterialEdit(id) {
|
||||
const url = "{% url 'material_json' 1 %}".replace('/1/json/', `/${id}/json/`);
|
||||
const res = await fetch(url, { credentials: 'same-origin' });
|
||||
const data = await res.json();
|
||||
|
||||
document.getElementById('materialModalTitle').textContent = 'Материал (правка)';
|
||||
document.getElementById('materialId').value = data.id;
|
||||
document.getElementById('materialCategory').value = data.category_id || '';
|
||||
document.getElementById('materialGrade').value = data.steel_grade_id || '';
|
||||
document.getElementById('materialName').value = data.name || '';
|
||||
document.getElementById('materialMassPerUnit').value = (data.mass_per_unit ?? '');
|
||||
|
||||
new bootstrap.Modal(document.getElementById('materialModal')).show();
|
||||
}
|
||||
|
||||
async function saveMaterial() {
|
||||
const fd = new FormData();
|
||||
fd.append('id', document.getElementById('materialId').value);
|
||||
fd.append('category_id', document.getElementById('materialCategory').value);
|
||||
fd.append('steel_grade_id', document.getElementById('materialGrade').value);
|
||||
fd.append('name', document.getElementById('materialName').value);
|
||||
fd.append('mass_per_unit', document.getElementById('materialMassPerUnit').value);
|
||||
|
||||
const res = await fetch("{% url 'material_upsert' %}", {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-CSRFToken': getCookie('csrftoken') },
|
||||
body: fd,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
alert('Не удалось сохранить материал');
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -25,6 +25,9 @@
|
||||
<input type="checkbox" class="btn-check" name="statuses" id="s_work" value="work" {% if 'work' in selected_statuses %}checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="btn btn-outline-primary btn-sm" for="s_work">В работе</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" name="statuses" id="s_leftover" value="leftover" {% if 'leftover' in selected_statuses %}checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="btn btn-outline-secondary btn-sm" for="s_leftover">Недодел</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" name="statuses" id="s_closed" value="closed" {% if 'closed' in selected_statuses %}checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="btn btn-outline-success btn-sm" for="s_closed">Завершено</label>
|
||||
{% else %}
|
||||
@@ -32,29 +35,14 @@
|
||||
<label class="btn btn-outline-primary btn-sm" for="s_work">В работе</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" name="statuses" id="s_leftover" value="leftover" {% if 'leftover' in selected_statuses %}checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="btn btn-outline-danger btn-sm" for="s_leftover">Недодел</label>
|
||||
<label class="btn btn-outline-secondary btn-sm" for="s_leftover">Недодел</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" name="statuses" id="s_closed" value="closed" {% if 'closed' in selected_statuses %}checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="btn btn-outline-success btn-sm" for="s_closed">Завершено</label>
|
||||
|
||||
{% if user_role in 'admin,technologist' %}
|
||||
<input type="checkbox" class="btn-check" name="statuses" id="s_imported" value="imported" {% if 'imported' in selected_statuses %}checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="btn btn-outline-accent btn-sm" for="s_imported">Импорт</label>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if user_role in 'admin,technologist,clerk' %}
|
||||
<div class="col-md-auto">
|
||||
<label class="small text-muted mb-1 fw-bold">Учёт 1С:</label>
|
||||
<select name="is_synced" class="form-select form-select-sm bg-body text-body border-secondary registry-filter-1c" onchange="this.form.submit()">
|
||||
<option value="" {% if not is_synced %}selected{% endif %}>Все</option>
|
||||
<option value="1" {% if is_synced == '1' %}selected{% endif %}>Учтено</option>
|
||||
<option value="0" {% if is_synced == '0' %}selected{% endif %}>Ожидает</option>
|
||||
</select>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-auto">
|
||||
<label class="small text-muted mb-1 fw-bold">С:</label>
|
||||
@@ -95,8 +83,7 @@
|
||||
const data = {
|
||||
statuses: Array.from(form.querySelectorAll('input[name="statuses"]:checked')).map(i=>i.value),
|
||||
m_ids: Array.from(form.querySelectorAll('input[name="m_ids"]:checked')).map(i=>i.value),
|
||||
start_date: s ? s.value : '',
|
||||
is_synced: (form.querySelector('select[name="is_synced"]')||{}).value || ''
|
||||
start_date: s ? s.value : ''
|
||||
};
|
||||
try { localStorage.setItem('registry_filters', JSON.stringify(data)); } catch(_){}
|
||||
}
|
||||
@@ -122,8 +109,6 @@
|
||||
}
|
||||
if (s) s.value = data.start_date || weekAgo;
|
||||
if (e) e.value = today;
|
||||
const sel = form.querySelector('select[name="is_synced"]');
|
||||
if (sel && data.is_synced !== undefined) sel.value = data.is_synced;
|
||||
const filtered = form.querySelector('input[name="filtered"]');
|
||||
if (filtered) filtered.value = '1';
|
||||
form.submit();
|
||||
|
||||
93
shiftflow/templates/shiftflow/partials/_workitems_table.html
Normal file
93
shiftflow/templates/shiftflow/partials/_workitems_table.html
Normal file
@@ -0,0 +1,93 @@
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle" data-sortable="1">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th data-sort-type="date">Дата</th>
|
||||
<th>Сделка</th>
|
||||
<th>Цех/Пост</th>
|
||||
<th>Наименование</th>
|
||||
<th>Материал</th>
|
||||
<th data-sort="false" class="text-center">Файлы</th>
|
||||
<th data-sort="false" style="width: 160px;">Прогресс</th>
|
||||
<th data-sort-type="number">План / Факт</th>
|
||||
<th>Статус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for wi in workitems %}
|
||||
<tr class="workitem-row" data-href="{% url 'workitem_detail' wi.id %}">
|
||||
<td class="small">{{ wi.date|date:"d.m.y" }}</td>
|
||||
<td><span class="text-accent fw-bold">{{ wi.deal.number|default:"-" }}</span></td>
|
||||
<td>
|
||||
{% if wi.machine %}
|
||||
<span class="badge bg-dark border border-secondary">{{ wi.workshop.name|default:"—" }}/{{ wi.machine.name }}</span>
|
||||
{% elif wi.workshop %}
|
||||
<span class="badge bg-dark border border-secondary">{{ wi.workshop.name }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="fw-bold">
|
||||
{{ wi.entity.drawing_number|default:"—" }} {{ wi.entity.name }}
|
||||
</td>
|
||||
<td class="small text-muted">
|
||||
{% if wi.entity.planned_material %}
|
||||
{{ wi.entity.planned_material.full_name|default:wi.entity.planned_material.name }}
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-center">
|
||||
{% if wi.entity.dxf_file %}
|
||||
<a href="{{ wi.entity.dxf_file.url }}" target="_blank" class="btn btn-sm btn-outline-info p-1 stop-prop" title="DXF/IGES/STEP">
|
||||
<i class="bi bi-file-earmark-code"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if wi.entity.pdf_main %}
|
||||
<a href="{{ wi.entity.pdf_main.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>
|
||||
<div class="progress bg-secondary-subtle border border-secondary sf-item-progress"
|
||||
style="height: 10px;"
|
||||
data-fact-width="{{ wi.fact_width|default:0 }}"
|
||||
title="Факт: {{ wi.fact_pct|default:0 }}%">
|
||||
<div class="progress-bar bg-warning sf-item-progress-bar"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span class="text-info fw-bold">{{ wi.quantity_plan }}</span> /
|
||||
<span class="text-success">{{ wi.quantity_done }}</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if wi.status == 'done' %}
|
||||
<span class="badge bg-success">{{ wi.get_status_display }}</span>
|
||||
{% elif wi.status == 'leftover' %}
|
||||
<span class="badge bg-secondary">{{ wi.get_status_display }}</span>
|
||||
{% else %}
|
||||
<span class="badge bg-primary">{{ wi.get_status_display }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="10" class="text-center p-5 text-muted">Записей WorkItem нет</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
document.querySelectorAll(".workitem-row").forEach(row => {
|
||||
row.addEventListener("click", function(e) {
|
||||
if (e.target.closest('.stop-prop')) return;
|
||||
const href = this.dataset.href;
|
||||
if (href) window.location.href = href;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
@@ -88,6 +88,10 @@
|
||||
<button type="button" class="btn btn-outline-accent btn-sm" id="openCompanyModalBtn">Создать</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-muted">Срок отгрузки</label>
|
||||
<input type="date" class="form-control border-secondary" id="dealDueDate">
|
||||
</div>
|
||||
<div class="mb-0">
|
||||
<label class="form-label small text-muted">Описание</label>
|
||||
<textarea class="form-control border-secondary" rows="3" id="dealDescription"></textarea>
|
||||
@@ -157,6 +161,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
const dealStatus = document.getElementById('dealStatus');
|
||||
const dealCompany = document.getElementById('dealCompany');
|
||||
const dealDescription = document.getElementById('dealDescription');
|
||||
const dealDueDate = document.getElementById('dealDueDate');
|
||||
const dealSaveBtn = document.getElementById('dealSaveBtn');
|
||||
const openCompanyModalBtn = document.getElementById('openCompanyModalBtn');
|
||||
|
||||
@@ -234,6 +239,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
dealId.value = '';
|
||||
dealNumber.value = '';
|
||||
dealDescription.value = '';
|
||||
if (dealDueDate) dealDueDate.value = '';
|
||||
if (dealStatus) dealStatus.value = 'work';
|
||||
if (dealCompany) dealCompany.value = '';
|
||||
});
|
||||
@@ -268,6 +274,7 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
status: dealStatus ? dealStatus.value : 'work',
|
||||
company_id: dealCompany.value,
|
||||
description: dealDescription.value,
|
||||
due_date: dealDueDate ? dealDueDate.value : '',
|
||||
};
|
||||
await postForm('{% url "deal_upsert" %}', payload);
|
||||
window.location.reload();
|
||||
|
||||
@@ -17,126 +17,752 @@
|
||||
<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,technologist' %}
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'task_add' %}?deal={{ deal.id }}&next={% url 'planning_deal' deal.id %}">
|
||||
<i class="bi bi-plus-lg me-1"></i>Добавить деталь
|
||||
</a>
|
||||
<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="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>Размер</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 tasks %}
|
||||
<tr class="task-row" style="cursor:pointer" data-href="{% url 'task_items' t.id %}">
|
||||
<td class="fw-bold">{{ t.drawing_name|default:"Б/ч" }}</td>
|
||||
<td class="small text-muted">{{ t.material.full_name|default:t.material.name }}</td>
|
||||
<td class="small">{{ t.size_value }}</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' %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-accent btn-sm"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#addToPlanModal"
|
||||
data-task-id="{{ t.id }}"
|
||||
data-task-name="{{ t.drawing_name|default:'Б/ч' }}"
|
||||
data-task-rem="{{ t.remaining_qty }}"
|
||||
>
|
||||
<i class="bi bi-plus-lg me-1"></i>В план
|
||||
</button>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="8" class="text-center p-5 text-muted">Деталей не найдено</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<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();">
|
||||
{% if user_role in 'admin,technologist' %}
|
||||
<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>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" disabled>В производство</button>
|
||||
{% endif %}
|
||||
</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' 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 %}
|
||||
</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-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="addToPlanModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal fade" id="startProductionModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" action="{% url 'planning_add' %}" class="modal-content border-secondary">
|
||||
<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>
|
||||
<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="task_id" id="modalTaskId">
|
||||
<div class="small text-muted mb-2" id="modalTaskTitle"></div>
|
||||
<div class="small text-muted mb-2" id="spTitle"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-muted d-block">Станок</label>
|
||||
<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="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 }}" required>
|
||||
<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="modalQty" required>
|
||||
<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="small text-muted" id="modalHint"></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">Добавить</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">
|
||||
{% 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">
|
||||
<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" placeholder="Опционально">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Наименование</label>
|
||||
<input class="form-control bg-body text-body border-secondary" id="diName" placeholder="Напр. Основание">
|
||||
</div>
|
||||
<div class="col-md-2 d-grid">
|
||||
<button type="button" class="btn btn-outline-secondary" id="diSearchBtn">Поиск</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mt-2">
|
||||
<div class="col-md-8">
|
||||
<label class="form-label">Найдено</label>
|
||||
<select class="form-select bg-body text-body border-secondary" id="diFound"></select>
|
||||
<input type="hidden" name="entity_id" id="diEntityId" required>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Кол-во, шт</label>
|
||||
<input class="form-control bg-body text-body border-secondary" name="quantity" id="diQty" value="1" required>
|
||||
</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>
|
||||
|
||||
<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');
|
||||
|
||||
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 = modalEl ? modalEl.querySelector('form') : null;
|
||||
|
||||
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');
|
||||
|
||||
if (!typeEl || !dnEl || !nameEl || !foundEl || !idEl) return;
|
||||
|
||||
function setSelectedFromFound() {
|
||||
idEl.value = foundEl.value || '';
|
||||
}
|
||||
|
||||
async function search(opts = { focusFound: false }) {
|
||||
const params = new URLSearchParams({
|
||||
entity_type: (typeEl.value || ''),
|
||||
q_dn: (dnEl.value || ''),
|
||||
q_name: (nameEl.value || ''),
|
||||
});
|
||||
const res = await fetch('{% url "entities_search" %}?' + params.toString(), { credentials: 'same-origin' });
|
||||
const data = await res.json();
|
||||
const items = (data && data.results) || [];
|
||||
|
||||
foundEl.innerHTML = '';
|
||||
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);
|
||||
});
|
||||
|
||||
if (items.length) {
|
||||
foundEl.value = String(items[0].id);
|
||||
setSelectedFromFound();
|
||||
if (opts && opts.focusFound) {
|
||||
foundEl.focus({ preventScroll: true });
|
||||
}
|
||||
} else {
|
||||
idEl.value = '';
|
||||
}
|
||||
|
||||
foundEl.onchange = setSelectedFromFound;
|
||||
return items;
|
||||
}
|
||||
|
||||
if (btn) btn.onclick = () => { search({ focusFound: true }); };
|
||||
|
||||
const onEnterSearch = (e) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
e.preventDefault();
|
||||
search({ focusFound: true });
|
||||
};
|
||||
|
||||
dnEl.addEventListener('keydown', onEnterSearch);
|
||||
nameEl.addEventListener('keydown', onEnterSearch);
|
||||
|
||||
foundEl.addEventListener('keydown', (e) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
e.preventDefault();
|
||||
setSelectedFromFound();
|
||||
if (idEl.value && formEl) {
|
||||
formEl.requestSubmit();
|
||||
} else {
|
||||
search({ focusFound: true });
|
||||
}
|
||||
});
|
||||
|
||||
if (modalEl) {
|
||||
modalEl.addEventListener('shown.bs.modal', () => {
|
||||
setTimeout(() => {
|
||||
dnEl.focus({ preventScroll: true });
|
||||
dnEl.select();
|
||||
}, 0);
|
||||
});
|
||||
}
|
||||
});
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
document.querySelectorAll('tr.task-row[data-href]').forEach(function (row) {
|
||||
row.addEventListener('click', function (e) {
|
||||
@@ -155,22 +781,39 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
if (planEl) planEl.style.width = `${plan}%`;
|
||||
});
|
||||
|
||||
const modal = document.getElementById('addToPlanModal');
|
||||
const modal = document.getElementById('workItemModal');
|
||||
if (!modal) return;
|
||||
|
||||
modal.addEventListener('shown.bs.modal', function (event) {
|
||||
const btn = event.relatedTarget;
|
||||
const taskId = btn.getAttribute('data-task-id');
|
||||
const name = btn.getAttribute('data-task-name');
|
||||
const entityId = btn.getAttribute('data-entity-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('modalTaskId').value = taskId;
|
||||
document.getElementById('modalTaskTitle').textContent = name;
|
||||
document.getElementById('modalHint').textContent = rem !== null ? `Осталось: ${rem} шт` : '';
|
||||
document.getElementById('wiEntityId').value = entityId;
|
||||
document.getElementById('wiOperationId').value = opId;
|
||||
|
||||
const qty = document.getElementById('modalQty');
|
||||
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));
|
||||
@@ -186,27 +829,65 @@ document.addEventListener('DOMContentLoaded', function () {
|
||||
qty.onkeydown = function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
const form = document.querySelector('#addToPlanModal form');
|
||||
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);
|
||||
selected = radios.find(r => r.value === savedMachine && !r.disabled);
|
||||
}
|
||||
|
||||
if (selected) {
|
||||
selected.checked = true;
|
||||
} else if (noneRadio) {
|
||||
noneRadio.checked = true;
|
||||
}
|
||||
if (!selected && radios.length) selected = radios[0];
|
||||
if (selected) selected.checked = true;
|
||||
|
||||
radios.forEach(r => {
|
||||
r.onchange = function () {
|
||||
try { localStorage.setItem('planning_machine_id', r.value); } catch (_) {}
|
||||
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 %}
|
||||
{% endblock %}
|
||||
|
||||
86
shiftflow/templates/shiftflow/planning_stages.html
Normal file
86
shiftflow/templates/shiftflow/planning_stages.html
Normal file
@@ -0,0 +1,86 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-3 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-graph-up-arrow me-2"></i>План (сделки)</h3>
|
||||
|
||||
<form class="d-flex flex-wrap gap-2 align-items-end" method="get">
|
||||
<div>
|
||||
<label class="form-label small text-muted mb-1">Поиск</label>
|
||||
<input class="form-control form-control-sm bg-body text-body border-secondary" name="q" value="{{ q }}" placeholder="№ сделки / заказчик">
|
||||
</div>
|
||||
<button class="btn btn-outline-accent btn-sm" type="submit"><i class="bi bi-search me-1"></i>Показать</button>
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'planning_stages' %}"><i class="bi bi-arrow-counterclockwise me-1"></i>Сброс</a>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="p-3">
|
||||
{% for c in deal_cards %}
|
||||
<div class="card border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-2 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<div class="fw-bold">
|
||||
<a class="text-decoration-none" href="{% url 'planning_deal' c.deal.id %}">{{ c.deal.number }}</a>
|
||||
<span class="text-muted small ms-2">{{ c.deal.company.name|default:"—" }}</span>
|
||||
{% if c.deal.due_date %}<span class="badge bg-secondary ms-2">{{ c.deal.due_date|date:"d.m.Y" }}</span>{% endif %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Позиция</th>
|
||||
<th class="text-center" style="width:120px;">Заказано</th>
|
||||
{% for op in op_columns %}
|
||||
<th class="text-center" style="min-width:140px;">
|
||||
{{ op.name }}
|
||||
<div class="small text-muted">{{ op.code }}</div>
|
||||
</th>
|
||||
{% endfor %}
|
||||
<th class="text-center" style="width:140px;">Готово</th>
|
||||
<th class="text-center" style="width:140px;">Отгружено</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in c.rows %}
|
||||
<tr style="cursor:pointer" onclick="window.location.href='{% url 'planning_deal' c.deal.id %}';">
|
||||
<td class="fw-bold">{{ r.entity }}</td>
|
||||
<td class="text-center">{{ r.need }}</td>
|
||||
{% for cell in r.op_cells %}
|
||||
<td class="text-center">
|
||||
{% if cell.has %}
|
||||
<div>{{ cell.done }}/{{ r.need }}</div>
|
||||
<div class="progress bg-secondary-subtle border border-secondary mt-1" style="height:6px;">
|
||||
<div class="progress-bar bg-warning" role="progressbar" style="width: {{ cell.pct }}%;"></div>
|
||||
</div>
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
{% endfor %}
|
||||
<td class="text-center">
|
||||
<div>{{ r.ready }}/{{ r.need }}</div>
|
||||
<div class="progress bg-secondary-subtle border border-secondary mt-1" style="height:6px;">
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: {{ r.ready_pct }}%;"></div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<div>{{ r.shipped }}/{{ r.need }}</div>
|
||||
<div class="progress bg-secondary-subtle border border-secondary mt-1" style="height:6px;">
|
||||
<div class="progress-bar bg-primary" role="progressbar" style="width: {{ r.shipped_pct }}%;"></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="text-muted">Нет сделок по выбранным фильтрам.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
218
shiftflow/templates/shiftflow/procurement_dashboard.html
Normal file
218
shiftflow/templates/shiftflow/procurement_dashboard.html
Normal file
@@ -0,0 +1,218 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary">
|
||||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||
<div class="w-100">
|
||||
<div class="d-flex flex-wrap align-items-center gap-3 justify-content-between">
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-truck me-2"></i>Снабжение</h3>
|
||||
|
||||
<form method="get" class="d-flex flex-wrap align-items-center gap-2">
|
||||
<input type="hidden" name="filtered" value="1">
|
||||
|
||||
<div class="d-flex w-100 mb-2 gap-2">
|
||||
<input type="text" class="form-control form-control-sm border-secondary" name="q" placeholder="Сделка / компонент" value="{{ q }}" style="max-width: 300px;">
|
||||
<button type="submit" class="btn btn-outline-accent btn-sm">
|
||||
<i class="bi bi-search me-1"></i>Применить
|
||||
</button>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'procurement' %}">Сброс</a>
|
||||
|
||||
<div class="ms-auto">
|
||||
<button type="submit" name="print" value="1" class="btn btn-outline-secondary btn-sm" formtarget="_blank">
|
||||
<i class="bi bi-printer me-1"></i>Печать
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-1">
|
||||
<input type="checkbox" class="btn-check" name="grouped" id="grouped" value="1" {% if grouped %}checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="btn btn-outline-accent btn-sm" for="grouped"><i class="bi bi-layers me-1"></i>Группировать</label>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap align-items-center gap-1">
|
||||
<span class="small text-muted me-1">Тип:</span>
|
||||
{% for code, label in type_choices %}
|
||||
<input type="checkbox" class="btn-check" name="types" id="type_{{ code }}" value="{{ code }}" {% if code in selected_types %}checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="btn btn-sm {% if code == 'raw' %}btn-outline-info{% elif code == 'purchased' %}btn-outline-primary{% elif code == 'casting' %}btn-outline-secondary{% elif code == 'outsourced' %}btn-outline-warning{% else %}btn-outline-secondary{% endif %}" for="type_{{ code }}">{{ label }}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap align-items-center gap-1">
|
||||
<span class="small text-muted me-1">Статус:</span>
|
||||
{% for code, label in status_choices %}
|
||||
<input type="checkbox" class="btn-check" name="statuses" id="st_{{ code }}" value="{{ code }}" {% if code in selected_statuses %}checked{% endif %} onchange="this.form.submit()">
|
||||
<label class="btn btn-sm {% if code == 'to_order' %}btn-outline-danger{% elif code == 'ordered' %}btn-outline-warning{% else %}btn-outline-success{% endif %}" for="st_{{ code }}">{{ label }}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-0">
|
||||
<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 style="width: 140px;">Тип</th>
|
||||
<th style="width: 140px;">К заказу</th>
|
||||
<th style="width: 220px;">Сделки</th>
|
||||
<th style="width: 140px;">Статус</th>
|
||||
<th style="width: 70px;" data-sort="false"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in requirements %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-semibold">{{ r.component_label }}</div>
|
||||
</td>
|
||||
<td class="small">
|
||||
{% if r.type == 'raw' %}Сырьё{% elif r.type == 'purchased' %}Покупное{% elif r.type == 'casting' %}Литьё{% elif r.type == 'outsourced' %}Аутсорс{% else %}—{% endif %}
|
||||
</td>
|
||||
<td class="fw-semibold">
|
||||
{{ r.required_qty }}{% if r.kind == 'raw' %} {{ r.unit }}{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.deals and r.deals|length > 0 %}
|
||||
{% for dn in r.deals|slice:":3" %}
|
||||
<span class="badge bg-secondary">{{ dn }}</span>
|
||||
{% endfor %}
|
||||
{% if r.deals|length > 3 %}
|
||||
<span class="badge bg-secondary" title="{{ r.deals|join:", " }}">…</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.status == 'to_order' %}
|
||||
<span class="badge bg-danger">К заказу</span>
|
||||
{% elif r.status == 'ordered' %}
|
||||
<span class="badge bg-warning text-dark">Заказано</span>
|
||||
{% else %}
|
||||
<span class="badge bg-success">Закрыто</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
{% if can_edit and not grouped and r.kind == 'component' %}
|
||||
{% if r.status == 'to_order' %}
|
||||
<form method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="mark_ordered">
|
||||
<input type="hidden" name="pr_id" value="{{ r.obj_id }}">
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||||
<button type="submit" class="btn btn-outline-accent btn-sm" title="Отметить как заказано">+</button>
|
||||
</form>
|
||||
{% elif r.status == 'ordered' %}
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-accent btn-sm"
|
||||
title="Оформить приход"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#receiveModal"
|
||||
data-pr-id="{{ r.obj_id }}"
|
||||
data-label="{{ r.component_label }}"
|
||||
data-deal-id="{{ r.deal_id }}"
|
||||
data-deal-number="{{ r.deals.0 }}"
|
||||
data-max="{{ r.required_qty }}"
|
||||
>+</button>
|
||||
{% else %}
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6" class="text-center p-5 text-muted">Потребностей не найдено</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="receiveModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<form method="post" class="modal-content border-secondary">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="receive_component">
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||||
<input type="hidden" name="pr_id" id="receivePrId">
|
||||
|
||||
<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 small text-muted" id="receiveLabel"></div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Склад</label>
|
||||
<select class="form-select border-secondary" name="location_id" required>
|
||||
{% for loc in locations %}
|
||||
<option value="{{ loc.id }}">{{ loc.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Сделка (опционально)</label>
|
||||
<select class="form-select border-secondary" name="deal_id" id="receiveDealId">
|
||||
<option value="">Свободно</option>
|
||||
{% for d in deals %}
|
||||
<option value="{{ d.id }}">{{ d.number }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Количество (шт)</label>
|
||||
<input class="form-control border-secondary" name="quantity" id="receiveQty" placeholder="Напр. 100" required>
|
||||
<div class="form-text">Количество уменьшит потребность выбранной строки. При достижении 0 статус станет «Закрыто».</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>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
const modal = document.getElementById('receiveModal');
|
||||
if (!modal) return;
|
||||
|
||||
const prId = document.getElementById('receivePrId');
|
||||
const label = document.getElementById('receiveLabel');
|
||||
const dealId = document.getElementById('receiveDealId');
|
||||
const qty = document.getElementById('receiveQty');
|
||||
|
||||
modal.addEventListener('show.bs.modal', function (event) {
|
||||
const btn = event.relatedTarget;
|
||||
if (!btn) return;
|
||||
const id = btn.getAttribute('data-pr-id') || '';
|
||||
const text = btn.getAttribute('data-label') || '';
|
||||
const max = btn.getAttribute('data-max') || '';
|
||||
const dId = btn.getAttribute('data-deal-id') || '';
|
||||
const dNum = btn.getAttribute('data-deal-number') || '';
|
||||
|
||||
if (prId) prId.value = id;
|
||||
if (label) label.textContent = (text ? ('Компонент: ' + text) : '') + (dNum ? (' · Сделка: ' + dNum) : '');
|
||||
if (dealId) dealId.value = dId;
|
||||
if (qty) {
|
||||
qty.value = max || '';
|
||||
qty.focus();
|
||||
qty.select();
|
||||
}
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
{% endblock %}
|
||||
70
shiftflow/templates/shiftflow/procurement_print.html
Normal file
70
shiftflow/templates/shiftflow/procurement_print.html
Normal file
@@ -0,0 +1,70 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Список к закупке</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; font-size: 12px; line-height: 1.4; color: #333; margin: 20px; }
|
||||
h2 { font-size: 18px; margin-bottom: 20px; text-align: center; }
|
||||
h3 { font-size: 14px; margin-top: 20px; margin-bottom: 10px; border-bottom: 1px solid #ccc; padding-bottom: 5px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-bottom: 20px; }
|
||||
th, td { border: 1px solid #ddd; padding: 6px 8px; }
|
||||
th { background-color: #f5f5f5; font-weight: bold; text-align: center; }
|
||||
td { text-align: left; vertical-align: top; }
|
||||
.text-center { text-align: center; }
|
||||
.text-right { text-align: right; }
|
||||
.marks { height: 26px; }
|
||||
@media print {
|
||||
body { margin: 0; }
|
||||
button { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="text-align: right; margin-bottom: 20px; display: flex; gap: 10px; justify-content: flex-end;">
|
||||
<button onclick="window.print()" style="padding: 5px 15px; cursor: pointer;">Распечатать</button>
|
||||
<button onclick="try { window.close(); } catch (e) {} if (!window.closed) { window.history.back(); }" style="padding: 5px 15px; cursor: pointer;">Закрыть</button>
|
||||
</div>
|
||||
|
||||
<h2>Список к закупке от {% now "d.m.Y" %}</h2>
|
||||
|
||||
{% for type_key, items in print_data.items %}
|
||||
<h3>
|
||||
{% for code, label in type_labels.items %}
|
||||
{% if code == type_key %}{{ label }}{% endif %}
|
||||
{% endfor %}
|
||||
</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 6%;">№ п/п</th>
|
||||
<th style="width: 24%;">Сделка(и)</th>
|
||||
<th style="width: 42%;">Наименование</th>
|
||||
<th style="width: 10%;">Кол-во, шт</th>
|
||||
<th style="width: 18%;">Отметки</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in items %}
|
||||
<tr>
|
||||
<td class="text-right">{{ forloop.counter }}</td>
|
||||
<td>
|
||||
{% if item.deals %}
|
||||
{{ item.deals|join:", " }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ item.component_label }}</td>
|
||||
<td class="text-right">{{ item.required_qty }}</td>
|
||||
<td class="marks"> </td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% empty %}
|
||||
<p class="text-center">Нет данных для печати по выбранным фильтрам.</p>
|
||||
{% endfor %}
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -4,6 +4,18 @@
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-3 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb" class="mb-1">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'products' %}">Изделия</a></li>
|
||||
{% for bc in breadcrumbs %}
|
||||
{% if forloop.last %}
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ bc.label }}</li>
|
||||
{% else %}
|
||||
<li class="breadcrumb-item"><a href="{{ bc.url }}">{{ bc.label }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-diagram-3 me-2"></i>{{ entity }}</h3>
|
||||
<div class="small text-muted mt-1">{{ entity.get_entity_type_display }}</div>
|
||||
</div>
|
||||
@@ -14,8 +26,8 @@
|
||||
<i class="bi bi-plus-lg me-1"></i>Добавить
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if parent_id %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'product_detail' parent_id %}">Назад</a>
|
||||
{% if back_url %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ back_url }}">Назад</a>
|
||||
{% else %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'products' %}">Назад</a>
|
||||
{% endif %}
|
||||
@@ -36,10 +48,20 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ln in lines %}
|
||||
<tr class="product-row" role="button" data-info-url="{% url 'product_info' ln.child.id %}">
|
||||
<tr class="product-row" role="button" data-href="{% url 'product_info' ln.child.id %}?next={{ request.get_full_path|urlencode }}">
|
||||
<td class="small text-muted">{{ ln.child.get_entity_type_display }}</td>
|
||||
<td class="fw-bold">{{ ln.child.drawing_number|default:"—" }}</td>
|
||||
<td>{{ ln.child.name }}</td>
|
||||
<td>
|
||||
{{ ln.child.name }}
|
||||
{% if ln.child.entity_type == 'product' or ln.child.entity_type == 'assembly' %}
|
||||
{% if ln.child.assembly_passport and ln.child.assembly_passport.requires_welding %}
|
||||
<span class="badge bg-secondary ms-2">Сварка</span>
|
||||
{% endif %}
|
||||
{% if ln.child.assembly_passport and ln.child.assembly_passport.requires_painting %}
|
||||
<span class="badge bg-secondary ms-1">Покраска</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if ln.child.passport_filled %}
|
||||
<i class="bi bi-check-circle-fill text-success" title="Заполнено"></i>
|
||||
@@ -59,7 +81,9 @@
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<div class="d-flex gap-2 justify-content-end">
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'product_detail' ln.child.id %}?parent={{ entity.id }}" onclick="event.stopPropagation();">Состав</a>
|
||||
{% if ln.child.entity_type == 'product' or ln.child.entity_type == 'assembly' %}
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'product_detail' ln.child.id %}?trail={{ trail_child }}" onclick="event.stopPropagation();">Состав</a>
|
||||
{% endif %}
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
@@ -225,20 +249,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" 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>
|
||||
|
||||
{% if request.GET.open == '1' %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
@@ -252,30 +262,10 @@
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const modalEl = document.getElementById('productInfoModal');
|
||||
const bodyEl = document.getElementById('productInfoBody');
|
||||
if (!modalEl || !bodyEl) return;
|
||||
|
||||
const modal = new bootstrap.Modal(modalEl);
|
||||
|
||||
async function openInfo(url) {
|
||||
bodyEl.innerHTML = '<div class="text-muted">Загрузка...</div>';
|
||||
modal.show();
|
||||
try {
|
||||
const nextUrl = encodeURIComponent(window.location.pathname + window.location.search);
|
||||
const sep = url.includes('?') ? '&' : '?';
|
||||
const res = await fetch(url + sep + 'next=' + nextUrl, { credentials: 'same-origin' });
|
||||
bodyEl.innerHTML = await res.text();
|
||||
} catch (e) {
|
||||
bodyEl.innerHTML = '<div class="alert alert-danger">Не удалось загрузить информацию.</div>';
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll('tr.product-row[data-info-url]').forEach(tr => {
|
||||
document.querySelectorAll('tr.product-row[data-href]').forEach(tr => {
|
||||
tr.addEventListener('click', () => {
|
||||
const url = tr.getAttribute('data-info-url');
|
||||
if (!url) return;
|
||||
openInfo(url);
|
||||
const url = tr.getAttribute('data-href');
|
||||
if (url) window.location.href = url;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,35 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-2 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'products' %}">Изделия</a></li>
|
||||
{% for bc in breadcrumbs %}
|
||||
{% if forloop.last %}
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ bc.label }}</li>
|
||||
{% else %}
|
||||
<li class="breadcrumb-item"><a href="{{ bc.url }}">{{ bc.label }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="small text-muted">{{ entity.get_entity_type_display }}</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
{% include 'components/_add_to_deal.html' %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ next }}">Назад</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="container-fluid p-0">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="save">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
@@ -36,14 +63,35 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if not can_edit %}
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Маршрут</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="route_id" {% if not can_edit %}disabled{% endif %}>
|
||||
<option value="">— не указано —</option>
|
||||
{% for r in routes %}
|
||||
<option value="{{ r.id }}" {% if entity.route_id == r.id %}selected{% endif %}>{{ r.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label class="form-label">Техпроцесс</label>
|
||||
<div class="form-control bg-body text-body border-secondary">
|
||||
{% if entity_ops %}
|
||||
{% for eo in entity_ops %}
|
||||
{{ eo.seq }}. {{ eo.operation.name }}{% if eo.operation.workshop %} ({{ eo.operation.workshop.name }}){% endif %}{% if not forloop.last %} · {% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
— не указан —
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Сварка</label>
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" name="requires_welding" id="rw" {% if passport and passport.requires_welding %}checked{% endif %} {% if not can_edit %}disabled{% endif %}>
|
||||
<label class="form-check-label" for="rw">Требуется сварка</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Покраска</label>
|
||||
<div class="form-check mt-2">
|
||||
<input class="form-check-input" type="checkbox" name="requires_painting" id="rp" {% if passport and passport.requires_painting %}checked{% endif %} {% if not can_edit %}disabled{% endif %}>
|
||||
<label class="form-check-label" for="rp">Требуется покраска</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
@@ -80,15 +128,82 @@
|
||||
</form>
|
||||
|
||||
{% if can_edit %}
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="mt-3 d-flex gap-2 align-items-center">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="create_route">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input class="form-control bg-body text-body border-secondary" name="route_name" placeholder="Новый маршрут">
|
||||
<button class="btn btn-outline-secondary" type="submit">Добавить маршрут</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<div class="fw-bold mb-2">Операции техпроцесса</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th style="width:70px;">№</th>
|
||||
<th>Операция</th>
|
||||
<th>Цех</th>
|
||||
<th data-sort="false" class="text-end"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for eo in entity_ops %}
|
||||
<tr>
|
||||
<td class="text-muted">{{ eo.seq }}</td>
|
||||
<td class="fw-bold">{{ eo.operation.name }}</td>
|
||||
<td class="small text-muted">{% if eo.operation.workshop %}{{ eo.operation.workshop.name }}{% else %}—{% endif %}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="move_entity_operation">
|
||||
<input type="hidden" name="direction" value="up">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">↑</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="move_entity_operation">
|
||||
<input type="hidden" name="direction" value="down">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">↓</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="delete_entity_operation">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4" class="text-center text-muted py-3">Операции не добавлены</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="row g-2 mt-2">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="add_entity_operation">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
|
||||
<div class="col-md-10">
|
||||
<label class="form-label">Добавить операцию</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="operation_id">
|
||||
<option value="">— выбери —</option>
|
||||
{% for op in operations %}
|
||||
<option value="{{ op.id }}">{{ op.name }}{% if op.workshop %} ({{ op.workshop.name }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end justify-content-end">
|
||||
<button class="btn btn-outline-accent" type="submit">+</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="fw-bold mb-2">Сварные швы</div>
|
||||
|
||||
@@ -110,9 +225,9 @@
|
||||
<td>{{ s.leg_mm }}</td>
|
||||
<td>{{ s.length_mm }}</td>
|
||||
<td>{{ s.quantity }}</td>
|
||||
<td class="text-end">
|
||||
<td class="text-end" onclick="event.stopPropagation();">
|
||||
{% if can_edit %}
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" onclick="event.stopPropagation();">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="delete_weld_seam">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
@@ -157,4 +272,233 @@
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="border-secondary my-4">
|
||||
|
||||
<div class="mt-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<div class="fw-bold">Состав</div>
|
||||
{% if can_edit %}
|
||||
<button type="button" class="btn btn-outline-accent btn-sm" data-bs-toggle="modal" data-bs-target="#bomAddModal">Добавить компонент</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Тип</th>
|
||||
<th>Обозначение</th>
|
||||
<th>Наименование</th>
|
||||
<th class="text-center">Кол-во</th>
|
||||
<th data-sort="false" class="text-end"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ln in bom_lines %}
|
||||
<tr role="button" style="cursor:pointer" onclick="window.location.href='{% url 'product_info' ln.child.id %}?next={{ request.get_full_path|urlencode }}&trail={{ trail_child|urlencode }}';">
|
||||
<td class="small text-muted">{{ ln.child.get_entity_type_display }}</td>
|
||||
<td class="fw-bold">{{ ln.child.drawing_number|default:"—" }}</td>
|
||||
<td>{{ ln.child.name }}</td>
|
||||
<td class="text-center" style="max-width:220px;" onclick="event.stopPropagation();">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="d-flex gap-2 align-items-center justify-content-center" onclick="event.stopPropagation();">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="bom_update_qty">
|
||||
<input type="hidden" name="bom_id" value="{{ ln.id }}">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input class="form-control form-control-sm bg-body text-body border-secondary" name="quantity" value="{{ ln.quantity }}" {% if not can_edit %}disabled{% endif %}>
|
||||
<button class="btn btn-outline-secondary btn-sm" type="submit" {% if not can_edit %}disabled{% endif %}>OK</button>
|
||||
</form>
|
||||
</td>
|
||||
<td class="text-end">
|
||||
{% if can_edit %}
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="bom_delete_line">
|
||||
<input type="hidden" name="bom_id" value="{{ ln.id }}">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<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-4">Пока нет компонентов</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if can_edit %}
|
||||
<div class="modal fade" id="bomAddModal" 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">
|
||||
<div class="border border-secondary rounded p-3 mb-3">
|
||||
<div class="fw-bold mb-2">Найти существующее</div>
|
||||
<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="bomType">
|
||||
<option value="assembly">Сборочная единица</option>
|
||||
<option value="part" selected>Деталь</option>
|
||||
<option value="purchased">Покупное</option>
|
||||
<option value="casting">Литьё</option>
|
||||
<option value="outsourced">Аутсорс</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Обозначение</label>
|
||||
<input class="form-control bg-body text-body border-secondary" id="bomDn" placeholder="Опционально">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Наименование</label>
|
||||
<input class="form-control bg-body text-body border-secondary" id="bomName" placeholder="Опционально">
|
||||
</div>
|
||||
<div class="col-md-2 d-grid">
|
||||
<button type="button" class="btn btn-outline-secondary" id="bomSearchBtn">Поиск</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="row g-2 align-items-end mt-2">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="bom_add_existing">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
|
||||
<div class="col-md-8">
|
||||
<label class="form-label">Найдено</label>
|
||||
<select class="form-select bg-body text-body border-secondary" id="bomFound"></select>
|
||||
<input type="hidden" name="child_id" id="bomChildId" required>
|
||||
</div>
|
||||
<div class="col-md-2">
|
||||
<label class="form-label">Кол-во, шт</label>
|
||||
<input class="form-control bg-body text-body border-secondary" name="quantity" value="1" required>
|
||||
</div>
|
||||
<div class="col-md-2 d-grid">
|
||||
<button class="btn btn-outline-accent" type="submit">Добавить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="border border-secondary rounded p-3">
|
||||
<div class="fw-bold mb-2">Создать новое и добавить</div>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="bom_create_and_add">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Тип</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="child_type" id="bomCreateType" required>
|
||||
<option value="assembly">Сборочная единица</option>
|
||||
<option value="part" selected>Деталь</option>
|
||||
<option value="purchased">Покупное</option>
|
||||
<option value="casting">Литьё</option>
|
||||
<option value="outsourced">Аутсорс</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Кол-во</label>
|
||||
<input class="form-control bg-body text-body border-secondary" name="quantity" value="1" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Обозначение</label>
|
||||
<input class="form-control bg-body text-body border-secondary" name="drawing_number" placeholder="Опционально">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Наименование</label>
|
||||
<input class="form-control bg-body text-body border-secondary" name="name" required>
|
||||
</div>
|
||||
<div class="col-12" id="bomPlannedMaterialBlock">
|
||||
<label class="form-label">Материал (для детали)</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="planned_material_id" id="bomPlannedMaterialSelect">
|
||||
<option value="">— не указано —</option>
|
||||
{% for m in materials %}
|
||||
<option value="{{ m.id }}">{{ m.full_name|default:m.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 d-flex justify-content-end mt-2">
|
||||
<button class="btn btn-outline-accent" type="submit">Создать и добавить</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const typeEl = document.getElementById('bomType');
|
||||
const dnEl = document.getElementById('bomDn');
|
||||
const nameEl = document.getElementById('bomName');
|
||||
const foundEl = document.getElementById('bomFound');
|
||||
const idEl = document.getElementById('bomChildId');
|
||||
const btn = document.getElementById('bomSearchBtn');
|
||||
|
||||
function setSelected() {
|
||||
if (idEl) idEl.value = (foundEl && foundEl.value) ? String(foundEl.value) : '';
|
||||
}
|
||||
|
||||
async function search() {
|
||||
const params = new URLSearchParams({
|
||||
entity_type: (typeEl && typeEl.value) || '',
|
||||
q_dn: (dnEl && dnEl.value) || '',
|
||||
q_name: (nameEl && nameEl.value) || '',
|
||||
});
|
||||
const res = await fetch('{% url "entities_search" %}?' + params.toString(), { credentials: 'same-origin' });
|
||||
const data = await res.json();
|
||||
const items = (data && data.results) || [];
|
||||
foundEl.innerHTML = '';
|
||||
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);
|
||||
});
|
||||
if (items.length) {
|
||||
foundEl.value = String(items[0].id);
|
||||
setSelected();
|
||||
} else {
|
||||
if (idEl) idEl.value = '';
|
||||
}
|
||||
foundEl.onchange = setSelected;
|
||||
}
|
||||
|
||||
if (btn) btn.addEventListener('click', search);
|
||||
|
||||
const createType = document.getElementById('bomCreateType');
|
||||
const block = document.getElementById('bomPlannedMaterialBlock');
|
||||
const matSel = document.getElementById('bomPlannedMaterialSelect');
|
||||
function syncMat() {
|
||||
const t = (createType && createType.value) || '';
|
||||
const isPart = t === 'part';
|
||||
if (block) block.style.display = isPart ? '' : 'none';
|
||||
if (matSel) {
|
||||
matSel.disabled = !isPart;
|
||||
if (!isPart) matSel.value = '';
|
||||
}
|
||||
}
|
||||
if (createType) {
|
||||
createType.addEventListener('change', syncMat);
|
||||
syncMat();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,8 +1,35 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-2 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'products' %}">Изделия</a></li>
|
||||
{% for bc in breadcrumbs %}
|
||||
{% if forloop.last %}
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ bc.label }}</li>
|
||||
{% else %}
|
||||
<li class="breadcrumb-item"><a href="{{ bc.url }}">{{ bc.label }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="small text-muted">{{ entity.get_entity_type_display }}</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
{% include 'components/_add_to_deal.html' %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ next }}">Назад</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="container-fluid p-0">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="save">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
@@ -38,15 +65,20 @@
|
||||
<input class="form-control bg-body text-body border-secondary" name="mass_kg" value="{% if passport and passport.mass_kg %}{{ passport.mass_kg }}{% endif %}" inputmode="decimal" {% if not can_edit %}disabled{% endif %}>
|
||||
</div>
|
||||
|
||||
{% if not can_edit %}
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Маршрут</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="route_id" {% if not can_edit %}disabled{% endif %}>
|
||||
<option value="">— не указано —</option>
|
||||
{% for r in routes %}
|
||||
<option value="{{ r.id }}" {% if entity.route_id == r.id %}selected{% endif %}>{{ r.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label class="form-label">Техпроцесс</label>
|
||||
<div class="form-control bg-body text-body border-secondary">
|
||||
{% if entity_ops %}
|
||||
{% for eo in entity_ops %}
|
||||
{{ eo.seq }}. {{ eo.operation.name }}{% if eo.operation.workshop %} ({{ eo.operation.workshop.name }}){% endif %}{% if not forloop.last %} · {% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Чертёж (PDF)</label>
|
||||
@@ -73,12 +105,81 @@
|
||||
</form>
|
||||
|
||||
{% if can_edit %}
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="mt-3 d-flex gap-2 align-items-center">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="create_route">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input class="form-control bg-body text-body border-secondary" name="route_name" placeholder="Новый маршрут">
|
||||
<button class="btn btn-outline-secondary" type="submit">Добавить маршрут</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<div class="fw-bold mb-2">Операции техпроцесса</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th style="width:70px;">№</th>
|
||||
<th>Операция</th>
|
||||
<th>Цех</th>
|
||||
<th data-sort="false" class="text-end"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for eo in entity_ops %}
|
||||
<tr>
|
||||
<td class="text-muted">{{ eo.seq }}</td>
|
||||
<td class="fw-bold">{{ eo.operation.name }}</td>
|
||||
<td class="small text-muted">{% if eo.operation.workshop %}{{ eo.operation.workshop.name }}{% else %}—{% endif %}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="move_entity_operation">
|
||||
<input type="hidden" name="direction" value="up">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">↑</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="move_entity_operation">
|
||||
<input type="hidden" name="direction" value="down">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">↓</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="delete_entity_operation">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4" class="text-center text-muted py-3">Операции не добавлены</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="row g-2 mt-2">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="add_entity_operation">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
|
||||
<div class="col-md-10">
|
||||
<label class="form-label">Добавить операцию</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="operation_id">
|
||||
<option value="">— выбери —</option>
|
||||
{% for op in operations %}
|
||||
<option value="{{ op.id }}">{{ op.name }}{% if op.workshop %} ({{ op.workshop.name }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end justify-content-end">
|
||||
<button class="btn btn-outline-accent" type="submit">+</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,7 +1,35 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-2 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'products' %}">Изделия</a></li>
|
||||
{% for bc in breadcrumbs %}
|
||||
{% if forloop.last %}
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ bc.label }}</li>
|
||||
{% else %}
|
||||
<li class="breadcrumb-item"><a href="{{ bc.url }}">{{ bc.label }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="small text-muted">{{ entity.get_entity_type_display }}</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
{% include 'components/_add_to_deal.html' %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ next }}">Назад</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="container-fluid p-0">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="save">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
@@ -33,4 +61,7 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,8 +1,35 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-2 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'products' %}">Изделия</a></li>
|
||||
{% for bc in breadcrumbs %}
|
||||
{% if forloop.last %}
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ bc.label }}</li>
|
||||
{% else %}
|
||||
<li class="breadcrumb-item"><a href="{{ bc.url }}">{{ bc.label }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="small text-muted">{{ entity.get_entity_type_display }}</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
{% include 'components/_add_to_deal.html' %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ next }}">Назад</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="container-fluid p-0">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="save">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
@@ -28,15 +55,20 @@
|
||||
<input class="form-control bg-body text-body border-secondary" name="name" value="{{ entity.name }}" {% if not can_edit %}disabled{% endif %}>
|
||||
</div>
|
||||
|
||||
{% if not can_edit %}
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Маршрут</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="route_id" {% if not can_edit %}disabled{% endif %}>
|
||||
<option value="">— не указано —</option>
|
||||
{% for r in routes %}
|
||||
<option value="{{ r.id }}" {% if entity.route_id == r.id %}selected{% endif %}>{{ r.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label class="form-label">Техпроцесс</label>
|
||||
<div class="form-control bg-body text-body border-secondary">
|
||||
{% if entity_ops %}
|
||||
{% for eo in entity_ops %}
|
||||
{{ eo.seq }}. {{ eo.operation.name }}{% if eo.operation.workshop %} ({{ eo.operation.workshop.name }}){% endif %}{% if not forloop.last %} · {% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
— не указан —
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Чертёж/ТЗ (PDF)</label>
|
||||
@@ -65,12 +97,81 @@
|
||||
</form>
|
||||
|
||||
{% if can_edit %}
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="mt-3 d-flex gap-2 align-items-center">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="create_route">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input class="form-control bg-body text-body border-secondary" name="route_name" placeholder="Новый маршрут">
|
||||
<button class="btn btn-outline-secondary" type="submit">Добавить маршрут</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<div class="fw-bold mb-2">Операции техпроцесса</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th style="width:70px;">№</th>
|
||||
<th>Операция</th>
|
||||
<th>Цех</th>
|
||||
<th data-sort="false" class="text-end"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for eo in entity_ops %}
|
||||
<tr>
|
||||
<td class="text-muted">{{ eo.seq }}</td>
|
||||
<td class="fw-bold">{{ eo.operation.name }}</td>
|
||||
<td class="small text-muted">{% if eo.operation.workshop %}{{ eo.operation.workshop.name }}{% else %}—{% endif %}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="move_entity_operation">
|
||||
<input type="hidden" name="direction" value="up">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">↑</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="move_entity_operation">
|
||||
<input type="hidden" name="direction" value="down">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">↓</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="delete_entity_operation">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4" class="text-center text-muted py-3">Операции не добавлены</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="row g-2 mt-2">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="add_entity_operation">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
|
||||
<div class="col-md-10">
|
||||
<label class="form-label">Добавить операцию</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="operation_id">
|
||||
<option value="">— выбери —</option>
|
||||
{% for op in operations %}
|
||||
<option value="{{ op.id }}">{{ op.name }}{% if op.workshop %} ({{ op.workshop.name }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end justify-content-end">
|
||||
<button class="btn btn-outline-accent" type="submit">+</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,8 +1,35 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-2 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'products' %}">Изделия</a></li>
|
||||
{% for bc in breadcrumbs %}
|
||||
{% if forloop.last %}
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ bc.label }}</li>
|
||||
{% else %}
|
||||
<li class="breadcrumb-item"><a href="{{ bc.url }}">{{ bc.label }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="small text-muted">{{ entity.get_entity_type_display }}</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
{% include 'components/_add_to_deal.html' %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ next }}">Назад</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="container-fluid p-0">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="save">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
@@ -38,15 +65,20 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{% if not can_edit %}
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Маршрут</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="route_id" {% if not can_edit %}disabled{% endif %}>
|
||||
<option value="">— не указано —</option>
|
||||
{% for r in routes %}
|
||||
<option value="{{ r.id }}" {% if entity.route_id == r.id %}selected{% endif %}>{{ r.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label class="form-label">Техпроцесс</label>
|
||||
<div class="form-control bg-body text-body border-secondary">
|
||||
{% if entity_ops %}
|
||||
{% for eo in entity_ops %}
|
||||
{{ eo.seq }}. {{ eo.operation.name }}{% if eo.operation.workshop %} ({{ eo.operation.workshop.name }}){% endif %}{% if not forloop.last %} · {% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
— не указан —
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-3">
|
||||
<label class="form-label">Толщина, мм</label>
|
||||
@@ -116,12 +148,81 @@
|
||||
</form>
|
||||
|
||||
{% if can_edit %}
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="mt-3 d-flex gap-2 align-items-center">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="create_route">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input class="form-control bg-body text-body border-secondary" name="route_name" placeholder="Новый маршрут">
|
||||
<button class="btn btn-outline-secondary" type="submit">Добавить маршрут</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<div class="fw-bold mb-2">Операции техпроцесса</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th style="width:70px;">№</th>
|
||||
<th>Операция</th>
|
||||
<th>Цех</th>
|
||||
<th data-sort="false" class="text-end"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for eo in entity_ops %}
|
||||
<tr>
|
||||
<td class="text-muted">{{ eo.seq }}</td>
|
||||
<td class="fw-bold">{{ eo.operation.name }}</td>
|
||||
<td class="small text-muted">{% if eo.operation.workshop %}{{ eo.operation.workshop.name }}{% else %}—{% endif %}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="move_entity_operation">
|
||||
<input type="hidden" name="direction" value="up">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">↑</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="move_entity_operation">
|
||||
<input type="hidden" name="direction" value="down">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">↓</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="delete_entity_operation">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4" class="text-center text-muted py-3">Операции не добавлены</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="row g-2 mt-2">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="add_entity_operation">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
|
||||
<div class="col-md-10">
|
||||
<label class="form-label">Добавить операцию</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="operation_id">
|
||||
<option value="">— выбери —</option>
|
||||
{% for op in operations %}
|
||||
<option value="{{ op.id }}">{{ op.name }}{% if op.workshop %} ({{ op.workshop.name }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end justify-content-end">
|
||||
<button class="btn btn-outline-accent" type="submit">+</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,8 +1,35 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-2 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'products' %}">Изделия</a></li>
|
||||
{% for bc in breadcrumbs %}
|
||||
{% if forloop.last %}
|
||||
<li class="breadcrumb-item active" aria-current="page">{{ bc.label }}</li>
|
||||
{% else %}
|
||||
<li class="breadcrumb-item"><a href="{{ bc.url }}">{{ bc.label }}</a></li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="small text-muted">{{ entity.get_entity_type_display }}</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
{% include 'components/_add_to_deal.html' %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ next }}">Назад</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="container-fluid p-0">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="save">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input type="hidden" name="trail" value="{{ request.GET.trail|default:'' }}">
|
||||
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
@@ -33,15 +60,20 @@
|
||||
<input class="form-control bg-body text-body border-secondary" name="gost" value="{% if passport %}{{ passport.gost }}{% endif %}" {% if not can_edit %}disabled{% endif %}>
|
||||
</div>
|
||||
|
||||
{% if not can_edit %}
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Маршрут</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="route_id" {% if not can_edit %}disabled{% endif %}>
|
||||
<option value="">— не указано —</option>
|
||||
{% for r in routes %}
|
||||
<option value="{{ r.id }}" {% if entity.route_id == r.id %}selected{% endif %}>{{ r.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<label class="form-label">Техпроцесс</label>
|
||||
<div class="form-control bg-body text-body border-secondary">
|
||||
{% if entity_ops %}
|
||||
{% for eo in entity_ops %}
|
||||
{{ eo.seq }}. {{ eo.operation.name }}{% if eo.operation.workshop %} ({{ eo.operation.workshop.name }}){% endif %}{% if not forloop.last %} · {% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
— не указан —
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Чертёж/паспорт (PDF)</label>
|
||||
@@ -68,12 +100,81 @@
|
||||
</form>
|
||||
|
||||
{% if can_edit %}
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="mt-3 d-flex gap-2 align-items-center">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="create_route">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<input class="form-control bg-body text-body border-secondary" name="route_name" placeholder="Новый маршрут">
|
||||
<button class="btn btn-outline-secondary" type="submit">Добавить маршрут</button>
|
||||
</form>
|
||||
<div class="mt-3">
|
||||
<div class="fw-bold mb-2">Операции техпроцесса</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th style="width:70px;">№</th>
|
||||
<th>Операция</th>
|
||||
<th>Цех</th>
|
||||
<th data-sort="false" class="text-end"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for eo in entity_ops %}
|
||||
<tr>
|
||||
<td class="text-muted">{{ eo.seq }}</td>
|
||||
<td class="fw-bold">{{ eo.operation.name }}</td>
|
||||
<td class="small text-muted">{% if eo.operation.workshop %}{{ eo.operation.workshop.name }}{% else %}—{% endif %}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="move_entity_operation">
|
||||
<input type="hidden" name="direction" value="up">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">↑</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="move_entity_operation">
|
||||
<input type="hidden" name="direction" value="down">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">↓</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'product_info' entity.id %}">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="delete_entity_operation">
|
||||
<input type="hidden" name="entity_operation_id" value="{{ eo.id }}">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
<button class="btn btn-outline-secondary" type="submit">Удалить</button>
|
||||
</form>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4" class="text-center text-muted py-3">Операции не добавлены</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{% url 'product_info' entity.id %}" class="row g-2 mt-2">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="add_entity_operation">
|
||||
<input type="hidden" name="next" value="{{ next }}">
|
||||
|
||||
<div class="col-md-10">
|
||||
<label class="form-label">Добавить операцию</label>
|
||||
<select class="form-select bg-body text-body border-secondary" name="operation_id">
|
||||
<option value="">— выбери —</option>
|
||||
{% for op in operations %}
|
||||
<option value="{{ op.id }}">{{ op.name }}{% if op.workshop %} ({{ op.workshop.name }}){% endif %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-2 d-flex align-items-end justify-content-end">
|
||||
<button class="btn btn-outline-accent" type="submit">+</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -12,16 +12,20 @@
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<form class="d-flex gap-2 align-items-center" method="get" action="{% url 'products' %}">
|
||||
<select class="form-select form-select-sm bg-body text-body border-secondary" name="entity_type" style="min-width: 220px;">
|
||||
<option value="" {% if not entity_type %}selected{% endif %}>Все типы</option>
|
||||
<option value="product" {% if entity_type == 'product' %}selected{% endif %}>Готовое изделие</option>
|
||||
<option value="assembly" {% if entity_type == 'assembly' %}selected{% endif %}>Сборочная единица</option>
|
||||
<option value="part" {% if entity_type == 'part' %}selected{% endif %}>Деталь</option>
|
||||
<option value="purchased" {% if entity_type == 'purchased' %}selected{% endif %}>Покупное</option>
|
||||
<option value="casting" {% if entity_type == 'casting' %}selected{% endif %}>Литьё</option>
|
||||
<option value="outsourced" {% if entity_type == 'outsourced' %}selected{% endif %}>Аутсорс</option>
|
||||
</select>
|
||||
<form class="d-flex flex-wrap gap-2 align-items-center" method="get" action="{% url 'products' %}">
|
||||
<div class="btn-group" role="group" aria-label="Фильтр типов">
|
||||
<input type="checkbox" class="btn-check" id="tProduct" name="types" value="product" {% if 'product' in entity_types %}checked{% endif %}>
|
||||
<label class="btn btn-outline-secondary btn-sm" for="tProduct">Изделие</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" id="tAssembly" name="types" value="assembly" {% if 'assembly' in entity_types %}checked{% endif %}>
|
||||
<label class="btn btn-outline-secondary btn-sm" for="tAssembly">СБ</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" id="tPart" name="types" value="part" {% if 'part' in entity_types %}checked{% endif %}>
|
||||
<label class="btn btn-outline-secondary btn-sm" for="tPart">Деталь</label>
|
||||
|
||||
<input type="checkbox" class="btn-check" id="tCasting" name="types" value="casting" {% if 'casting' in entity_types %}checked{% endif %}>
|
||||
<label class="btn btn-outline-secondary btn-sm" for="tCasting">Литьё</label>
|
||||
</div>
|
||||
|
||||
<input class="form-control form-control-sm bg-body text-body border-secondary" name="q" value="{{ q }}" placeholder="Поиск (обозначение/наименование)" style="min-width: 320px;">
|
||||
<button class="btn btn-outline-secondary btn-sm" type="submit"><i class="bi bi-search me-1"></i>Найти</button>
|
||||
@@ -39,12 +43,11 @@
|
||||
<th>Наименование</th>
|
||||
<th>Материал</th>
|
||||
<th>Заполнен</th>
|
||||
<th data-sort="false"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in products %}
|
||||
<tr class="product-row" role="button" data-info-url="{% url 'product_info' p.id %}">
|
||||
<tr class="product-row" role="button" data-href="{% url 'product_info' p.id %}?next={{ request.get_full_path|urlencode }}">
|
||||
<td class="small text-muted">{{ p.get_entity_type_display }}</td>
|
||||
<td class="fw-bold">{{ p.drawing_number|default:"—" }}</td>
|
||||
<td>{{ p.name }}</td>
|
||||
@@ -62,11 +65,6 @@
|
||||
<i class="bi bi-circle text-muted" title="Не заполнено"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'product_detail' p.id %}" onclick="event.stopPropagation();">
|
||||
{% if p.entity_type == 'product' or p.entity_type == 'assembly' %}Состав{% else %}Открыть{% endif %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5" class="text-center text-muted py-4">Пока ничего нет</td></tr>
|
||||
@@ -115,46 +113,32 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" 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>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const modalEl = document.getElementById('productInfoModal');
|
||||
const bodyEl = document.getElementById('productInfoBody');
|
||||
if (!modalEl || !bodyEl) return;
|
||||
document.querySelectorAll('tr.product-row[data-href]').forEach(tr => {
|
||||
tr.addEventListener('click', () => {
|
||||
const url = tr.getAttribute('data-href');
|
||||
if (url) window.location.href = url;
|
||||
});
|
||||
});
|
||||
|
||||
const modal = new bootstrap.Modal(modalEl);
|
||||
const form = document.querySelector('form[action="{% url 'products' %}"][method="get"]');
|
||||
if (!form) return;
|
||||
|
||||
async function openInfo(url) {
|
||||
bodyEl.innerHTML = '<div class="text-muted">Загрузка...</div>';
|
||||
modal.show();
|
||||
try {
|
||||
const nextUrl = encodeURIComponent(window.location.pathname + window.location.search);
|
||||
const sep = url.includes('?') ? '&' : '?';
|
||||
const res = await fetch(url + sep + 'next=' + nextUrl, { credentials: 'same-origin' });
|
||||
bodyEl.innerHTML = await res.text();
|
||||
} catch (e) {
|
||||
bodyEl.innerHTML = '<div class="alert alert-danger">Не удалось загрузить информацию.</div>';
|
||||
function ensureAtLeastOneTypeChecked() {
|
||||
const boxes = Array.from(form.querySelectorAll('input[type="checkbox"][name="types"]'));
|
||||
if (!boxes.length) return;
|
||||
const anyChecked = boxes.some(b => b.checked);
|
||||
if (!anyChecked) {
|
||||
const productBox = boxes.find(b => b.value === 'product');
|
||||
if (productBox) productBox.checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
document.querySelectorAll('tr.product-row[data-info-url]').forEach(tr => {
|
||||
tr.addEventListener('click', () => {
|
||||
const url = tr.getAttribute('data-info-url');
|
||||
if (!url) return;
|
||||
openInfo(url);
|
||||
form.querySelectorAll('input[type="checkbox"][name="types"]').forEach(cb => {
|
||||
cb.addEventListener('change', () => {
|
||||
ensureAtLeastOneTypeChecked();
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -7,12 +7,12 @@
|
||||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-list-task me-2"></i>Реестр заданий</h3>
|
||||
{% if user_role in 'admin,technologist,master' %}
|
||||
<a class="btn btn-outline-secondary btn-sm" target="_blank" href="{% url 'registry_print' %}?{{ request.GET.urlencode }}">
|
||||
<a class="btn btn-outline-secondary btn-sm" target="_blank" href="{% url 'registry_workitems_print' %}?{{ request.GET.urlencode }}">
|
||||
<i class="bi bi-printer me-1"></i>Печать
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include 'shiftflow/partials/_items_table.html' with items=items %}
|
||||
|
||||
{% include 'shiftflow/partials/_workitems_table.html' with workitems=workitems %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
86
shiftflow/templates/shiftflow/registry_workitems_print.html
Normal file
86
shiftflow/templates/shiftflow/registry_workitems_print.html
Normal file
@@ -0,0 +1,86 @@
|
||||
{% load static %}
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Реестр заданий (WorkItem)</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<link rel="stylesheet" href="{% static 'css/style.css' %}">
|
||||
<style>
|
||||
body { background: #fff; color: #000; }
|
||||
.print-table { width: 100%; border-collapse: collapse; }
|
||||
.print-table th, .print-table td { border: 1px solid #000; padding: 4px 6px; font-size: 12px; vertical-align: top; }
|
||||
.print-header { display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 8px; }
|
||||
.print-title { font-size: 16px; font-weight: 700; margin: 0; }
|
||||
.print-meta { font-size: 12px; }
|
||||
.center { text-align: center; }
|
||||
@media print {
|
||||
.no-print { display: none !important; }
|
||||
.page { page-break-after: always; }
|
||||
.page:last-child { page-break-after: auto; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid my-3 no-print d-flex gap-2">
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="window.print()">Печать</button>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="window.close()">Закрыть</button>
|
||||
<div class="ms-auto small text-muted">
|
||||
{{ printed_at|date:"d.m.Y H:i" }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% for g in groups %}
|
||||
<div class="container-fluid page my-3">
|
||||
<div class="print-header">
|
||||
<div>
|
||||
<h1 class="print-title">Реестр заданий</h1>
|
||||
<div class="print-meta">
|
||||
Цех: <strong>{{ g.workshop }}</strong>
|
||||
{% if g.machine %} · Станок: <strong>{{ g.machine }}</strong>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="print-meta text-end">
|
||||
{% if print_date %}Дата: <strong>{{ print_date|date:"d.m.y" }}</strong>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<table class="print-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80px;">Дата</th>
|
||||
<th style="width:90px;">Сделка</th>
|
||||
<th style="width:180px;">Операция</th>
|
||||
<th>Позиция</th>
|
||||
<th style="width:160px;">Материал</th>
|
||||
<th style="width:80px;" class="center">План</th>
|
||||
<th style="width:80px;" class="center">Факт</th>
|
||||
<th style="width:90px;">Статус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for wi in g.items %}
|
||||
<tr>
|
||||
<td class="center">{{ wi.date|date:"d.m.y" }}</td>
|
||||
<td class="center">{{ wi.deal.number|default:"-" }}</td>
|
||||
<td>{{ wi.operation.name|default:wi.stage|default:"—" }}</td>
|
||||
<td>{{ wi.entity.drawing_number|default:"—" }} {{ wi.entity.name }}</td>
|
||||
<td>
|
||||
{% if wi.entity.planned_material %}
|
||||
{{ wi.entity.planned_material.full_name|default:wi.entity.planned_material.name }}
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="center">{{ wi.quantity_plan }}</td>
|
||||
<td class="center">{{ wi.quantity_done }}</td>
|
||||
<td>{{ wi.status|default:"planned" }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</body>
|
||||
</html>
|
||||
135
shiftflow/templates/shiftflow/steel_grades_catalog.html
Normal file
135
shiftflow/templates/shiftflow/steel_grades_catalog.html
Normal file
@@ -0,0 +1,135 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-3 d-flex flex-wrap justify-content-between align-items-center gap-2">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb" class="small">
|
||||
<ol class="breadcrumb mb-1">
|
||||
<li class="breadcrumb-item"><a class="text-decoration-none" href="{% url 'directories' %}">Справочники</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Марки стали</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-grid-3x3-gap me-2"></i>Марки стали</h3>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<form method="get" class="d-flex gap-2 align-items-center">
|
||||
<input class="form-control form-control-sm bg-body text-body border-secondary" name="q" value="{{ q }}" placeholder="Поиск...">
|
||||
<button class="btn btn-outline-accent btn-sm" type="submit"><i class="bi bi-search me-1"></i>Найти</button>
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'steel_grades_catalog' %}"><i class="bi bi-arrow-counterclockwise me-1"></i>Сброс</a>
|
||||
</form>
|
||||
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'directories' %}"><i class="bi bi-arrow-left me-1"></i>Назад</a>
|
||||
|
||||
{% if can_edit %}
|
||||
<button class="btn btn-outline-accent btn-sm" type="button" data-bs-toggle="modal" data-bs-target="#gradeModal" onclick="openGradeCreate()">
|
||||
<i class="bi bi-plus-lg me-1"></i>Создать
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Название</th>
|
||||
<th>ГОСТ/ТУ</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for g in grades %}
|
||||
<tr role="button" {% if can_edit %}onclick="openGradeEdit(this)"{% endif %}
|
||||
data-id="{{ g.id }}" data-name="{{ g.name }}" data-gost="{{ g.gost_standard }}">
|
||||
<td class="fw-bold">{{ g.name }}</td>
|
||||
<td>{{ g.gost_standard|default:"—" }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="2" class="text-center text-muted py-4">Нет данных</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="gradeModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<form class="modal-content border-secondary" onsubmit="event.preventDefault(); saveGrade();">
|
||||
<div class="modal-header border-secondary">
|
||||
<h5 class="modal-title" id="gradeModalTitle">Марка стали</h5>
|
||||
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
|
||||
</div>
|
||||
|
||||
<div class="modal-body">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" id="gradeId">
|
||||
|
||||
<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" id="gradeName" required {% if not can_edit %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">ГОСТ/ТУ</label>
|
||||
<input class="form-control bg-body text-body border-secondary" id="gradeGost" {% if not can_edit %}disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer border-secondary">
|
||||
<button type="button" class="btn btn-outline-accent" data-bs-dismiss="modal">Отмена</button>
|
||||
{% if can_edit %}
|
||||
<button type="submit" class="btn btn-outline-accent">Сохранить</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
return '';
|
||||
}
|
||||
|
||||
function openGradeCreate() {
|
||||
document.getElementById('gradeModalTitle').textContent = 'Марка стали (создание)';
|
||||
document.getElementById('gradeId').value = '';
|
||||
document.getElementById('gradeName').value = '';
|
||||
document.getElementById('gradeGost').value = '';
|
||||
new bootstrap.Modal(document.getElementById('gradeModal')).show();
|
||||
}
|
||||
|
||||
function openGradeEdit(tr) {
|
||||
document.getElementById('gradeModalTitle').textContent = 'Марка стали (правка)';
|
||||
document.getElementById('gradeId').value = tr.getAttribute('data-id') || '';
|
||||
document.getElementById('gradeName').value = tr.getAttribute('data-name') || '';
|
||||
document.getElementById('gradeGost').value = tr.getAttribute('data-gost') || '';
|
||||
new bootstrap.Modal(document.getElementById('gradeModal')).show();
|
||||
}
|
||||
|
||||
async function saveGrade() {
|
||||
const fd = new FormData();
|
||||
fd.append('id', document.getElementById('gradeId').value);
|
||||
fd.append('name', document.getElementById('gradeName').value);
|
||||
fd.append('gost_standard', document.getElementById('gradeGost').value);
|
||||
|
||||
const res = await fetch("{% url 'steel_grade_upsert' %}", {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'X-CSRFToken': getCookie('csrftoken') },
|
||||
body: fd,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
alert('Не удалось сохранить марку стали');
|
||||
return;
|
||||
}
|
||||
|
||||
window.location.reload();
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
141
shiftflow/templates/shiftflow/supply_catalog.html
Normal file
141
shiftflow/templates/shiftflow/supply_catalog.html
Normal file
@@ -0,0 +1,141 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-3 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<div>
|
||||
<nav aria-label="breadcrumb" class="small">
|
||||
<ol class="breadcrumb mb-1">
|
||||
<li class="breadcrumb-item"><a class="text-decoration-none" href="{% url 'directories' %}">Справочники</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Номенклатура снабжения</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-box-seam me-2"></i>Номенклатура снабжения</h3>
|
||||
<div class="small text-muted">Покупное / Аутсорс</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex flex-wrap gap-2 align-items-center">
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'directories' %}"><i class="bi bi-arrow-left me-1"></i>Назад</a>
|
||||
|
||||
{% if can_edit %}
|
||||
<button type="button" class="btn btn-outline-accent btn-sm" data-bs-toggle="modal" data-bs-target="#createSupplyModal">
|
||||
<i class="bi bi-plus-lg me-1"></i>Создать
|
||||
</button>
|
||||
{% endif %}
|
||||
|
||||
<form class="d-flex flex-wrap gap-2 align-items-center" method="get" action="{% url 'supply_catalog' %}">
|
||||
<div class="btn-group" role="group" aria-label="Фильтр типов">
|
||||
{% for code, label in type_choices %}
|
||||
<input type="checkbox" class="btn-check" id="t{{ code }}" name="types" value="{{ code }}" {% if code in entity_types %}checked{% endif %}>
|
||||
<label class="btn btn-outline-secondary btn-sm" for="t{{ code }}">{{ label }}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<input class="form-control form-control-sm bg-body text-body border-secondary" name="q" value="{{ q }}" placeholder="Поиск (обозначение/наименование)" style="min-width: 320px;">
|
||||
<button class="btn btn-outline-accent btn-sm" type="submit"><i class="bi bi-search me-1"></i>Найти</button>
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'supply_catalog' %}"><i class="bi bi-arrow-counterclockwise me-1"></i>Сброс</a>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Тип</th>
|
||||
<th>Обозначение</th>
|
||||
<th>Наименование</th>
|
||||
<th>Заполнен</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for p in items %}
|
||||
<tr class="product-row" role="button" data-href="{% url 'product_info' p.id %}?next={{ request.get_full_path|urlencode }}">
|
||||
<td class="small text-muted">{{ p.get_entity_type_display }}</td>
|
||||
<td class="fw-bold">{{ p.drawing_number|default:"—" }}</td>
|
||||
<td>{{ p.name }}</td>
|
||||
<td>
|
||||
{% if p.passport_filled %}
|
||||
<i class="bi bi-check-circle-fill text-success" title="Заполнено"></i>
|
||||
{% else %}
|
||||
<i class="bi bi-circle text-muted" title="Не заполнено"></i>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4" class="text-center text-muted py-4">Пока ничего нет</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" id="createSupplyModal" tabindex="-1" aria-hidden="true">
|
||||
<div class="modal-dialog modal-lg">
|
||||
<form method="post" action="{% url 'supply_catalog' %}" class="modal-content border-secondary">
|
||||
{% csrf_token %}
|
||||
<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-4">
|
||||
<label class="form-label">Тип</label>
|
||||
<select class="form-select" name="entity_type" required>
|
||||
<option value="purchased">Покупное</option>
|
||||
<option value="outsourced">Аутсорс</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Обозначение/Артикул</label>
|
||||
<input class="form-control" name="drawing_number" placeholder="Напр. МАСКА-001">
|
||||
</div>
|
||||
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Наименование</label>
|
||||
<input class="form-control" name="name" placeholder="Напр. Маска сварочная хамелеон" required>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="modal-footer border-secondary">
|
||||
<button type="button" class="btn btn-outline-accent" data-bs-dismiss="modal">Отмена</button>
|
||||
<button type="submit" class="btn btn-outline-accent">Создать</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.querySelectorAll('tr.product-row[data-href]').forEach(tr => {
|
||||
tr.addEventListener('click', () => {
|
||||
const url = tr.getAttribute('data-href');
|
||||
if (url) window.location.href = url;
|
||||
});
|
||||
});
|
||||
|
||||
const form = document.querySelector('form[action="{% url 'supply_catalog' %}"][method="get"]');
|
||||
if (!form) return;
|
||||
|
||||
function ensureAtLeastOneTypeChecked() {
|
||||
const boxes = Array.from(form.querySelectorAll('input[type="checkbox"][name="types"]'));
|
||||
if (!boxes.length) return;
|
||||
const anyChecked = boxes.some(b => b.checked);
|
||||
if (!anyChecked) {
|
||||
boxes[0].checked = true;
|
||||
}
|
||||
}
|
||||
|
||||
form.querySelectorAll('input[type="checkbox"][name="types"]').forEach(cb => {
|
||||
cb.addEventListener('change', () => {
|
||||
ensureAtLeastOneTypeChecked();
|
||||
form.submit();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
308
shiftflow/templates/shiftflow/workitem_detail.html
Normal file
308
shiftflow/templates/shiftflow/workitem_detail.html
Normal file
@@ -0,0 +1,308 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10 col-xl-8">
|
||||
<div class="card shadow-sm border-secondary mb-4">
|
||||
<div class="card-header border-secondary d-flex justify-content-between align-items-center py-3">
|
||||
<h3 class="text-accent mb-0">
|
||||
<i class="bi bi-info-circle me-2"></i>
|
||||
<a href="{% url 'workitem_entity_list' workitem.deal.id workitem.entity.id %}" class="text-decoration-none text-reset">
|
||||
{{ workitem.entity.drawing_number|default:"—" }} {{ workitem.entity.name }}
|
||||
</a>
|
||||
{% if can_edit_entity %}
|
||||
<a class="ms-2 text-decoration-none" href="{% url 'product_info' workitem.entity.id %}?next={{ request.path|urlencode }}" title="Открыть паспорт">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</h3>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<a href="{% url 'planning_deal' workitem.deal.id %}" class="text-decoration-none">
|
||||
<span class="badge bg-secondary">Сделка № {{ workitem.deal.number }}</span>
|
||||
</a>
|
||||
|
||||
{% if user_role in 'admin,master,operator,prod_head' and close_url %}
|
||||
<a class="btn btn-outline-warning btn-sm" href="{{ close_url }}">
|
||||
<i class="bi bi-check2-square me-1"></i>{{ close_label|default:'Закрыть' }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
{% if user_role in 'admin,technologist,master,clerk' %}
|
||||
{% if workitem.entity.entity_type == 'product' or workitem.entity.entity_type == 'assembly' %}
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'workitem_kitting' workitem.id %}?next={{ request.get_full_path|urlencode }}">
|
||||
<i class="bi bi-box-seam me-1"></i>Комплектация
|
||||
</a>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-4">
|
||||
<form method="post" action="{% url 'workitem_update' %}" class="mb-4" id="workitemForm">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="workitem_id" value="{{ workitem.id }}">
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||||
|
||||
<div class="row g-3 mb-4 border-bottom border-secondary pb-3 text-body">
|
||||
<div class="col-md-4">
|
||||
<small class="text-muted d-block">Дата</small>
|
||||
{% if user_role in 'admin,technologist' %}
|
||||
<input type="date" class="form-control border-secondary" name="date" value="{{ workitem.date|date:'Y-m-d' }}">
|
||||
{% else %}
|
||||
<strong>{{ workitem.date|date:"d.m.Y" }}</strong>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<small class="text-muted d-block">Цех/Пост</small>
|
||||
{% if user_role in 'admin,technologist,master' %}
|
||||
<select name="machine_id" class="form-select border-secondary">
|
||||
<option value="">— без станка —</option>
|
||||
{% for m in machines %}
|
||||
<option value="{{ m.id }}" {% if workitem.machine_id == m.id %}selected{% endif %}>{{ m.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<strong>
|
||||
{% if workitem.machine %}{{ workitem.machine.name }}{% elif workitem.workshop %}{{ workitem.workshop.name }}{% else %}—{% endif %}
|
||||
</strong>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<small class="text-muted d-block">Операция</small>
|
||||
<strong>{% if workitem.operation %}{{ workitem.operation.name }}{% else %}{{ workitem.stage|default:"—" }}{% endif %}</strong>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4 border-bottom border-secondary pb-3 text-body">
|
||||
<div class="col-md-4">
|
||||
<small class="text-muted d-block">Материал (паспорт)</small>
|
||||
<strong>
|
||||
{% if workitem.entity.planned_material %}
|
||||
{{ workitem.entity.planned_material.full_name|default:workitem.entity.planned_material.name }}
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</strong>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<small class="text-muted d-block">План</small>
|
||||
{% if user_role in 'admin,technologist' %}
|
||||
<input type="number" min="0" class="form-control border-secondary" name="quantity_plan" value="{{ workitem.quantity_plan }}">
|
||||
{% else %}
|
||||
<strong class="text-info fs-5">{{ workitem.quantity_plan }} шт.</strong>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<small class="text-muted d-block">Факт</small>
|
||||
{% if user_role in 'admin,technologist,master,operator' %}
|
||||
<input type="number" min="0" class="form-control border-secondary" name="quantity_done" id="wiDone" value="{{ workitem.quantity_done }}">
|
||||
{% else %}
|
||||
<strong class="text-success fs-5">{{ workitem.quantity_done }} шт.</strong>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-4 border-bottom border-secondary pb-3 text-body">
|
||||
<div class="col-md-4">
|
||||
<small class="text-muted d-block">Статус задания</small>
|
||||
{% if user_role in 'admin,technologist' %}
|
||||
<select class="form-select border-secondary" name="workitem_status">
|
||||
{% for val, label in workitem_status_choices %}
|
||||
<option value="{{ val }}" {% if workitem.status == val %}selected{% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% else %}
|
||||
<strong>{{ workitem.get_status_display }}</strong>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-12">
|
||||
{% if workitem.comment %}
|
||||
<div class="alert alert-warning border border-warning sf-attention mb-3">
|
||||
<div class="d-flex align-items-start gap-2">
|
||||
<i class="bi bi-exclamation-triangle"></i>
|
||||
<div class="fw-bold">{{ workitem.comment }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if user_role in 'admin,technologist,master' %}
|
||||
<small class="text-muted d-block">Комментарий</small>
|
||||
<textarea class="form-control border-secondary" name="comment" rows="2" placeholder="Указания/заметки">{{ workitem.comment }}</textarea>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
{% if workitem.entity.entity_type == 'part' %}
|
||||
<div class="mb-4">
|
||||
<div class="small text-muted mb-2">Превью</div>
|
||||
<div class="row g-3">
|
||||
<div class="col-md-8">
|
||||
<div class="border border-secondary rounded p-2" style="height: 200px; overflow: hidden;">
|
||||
{% if workitem.entity.preview %}
|
||||
<img src="{{ workitem.entity.preview.url }}" alt="Превью" style="max-width:100%; max-height:100%; object-fit:contain; display:block; margin:0 auto;">
|
||||
{% else %}
|
||||
<div style="width:100%; height:100%;"></div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="mb-3">
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
{% if workitem.entity.dxf_file %}
|
||||
<a href="{{ workitem.entity.dxf_file.url }}" target="_blank" class="btn btn-sm btn-outline-info p-1" title="DXF/STEP">
|
||||
<i class="bi bi-file-earmark-code"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-outline-secondary p-1 disabled" title="DXF/STEP">
|
||||
<i class="bi bi-file-earmark-code"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
<div class="small text-muted">DXF/STEP</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div class="d-flex align-items-center gap-2 mb-2">
|
||||
{% if workitem.entity.pdf_main %}
|
||||
<a href="{{ workitem.entity.pdf_main.url }}" target="_blank" class="btn btn-sm btn-outline-danger p-1" title="PDF">
|
||||
<i class="bi bi-file-pdf"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="btn btn-sm btn-outline-secondary p-1 disabled" title="PDF">
|
||||
<i class="bi bi-file-pdf"></i>
|
||||
</span>
|
||||
{% endif %}
|
||||
<div class="small text-muted">PDF</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if can_edit_entity and workitem.entity.dxf_file %}
|
||||
<form method="post" action="{% url 'product_preview_update' workitem.entity.id %}" class="mt-3">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="next" value="{{ request.path }}">
|
||||
<button type="submit" class="btn btn-outline-secondary btn-sm">
|
||||
{% if workitem.entity.preview %}Обновить превью DXF{% else %}Сгенерировать превью DXF{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="mb-4">
|
||||
<div class="small text-muted mb-2">Файлы</div>
|
||||
<div class="d-flex gap-2">
|
||||
{% if workitem.entity.dxf_file %}
|
||||
<a href="{{ workitem.entity.dxf_file.url }}" target="_blank" class="btn btn-sm btn-outline-info" title="DXF/STEP">
|
||||
<i class="bi bi-file-earmark-code me-1"></i>DXF/STEP
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if workitem.entity.pdf_main %}
|
||||
<a href="{{ workitem.entity.pdf_main.url }}" target="_blank" class="btn btn-sm btn-outline-danger" title="PDF">
|
||||
<i class="bi bi-file-pdf me-1"></i>PDF
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if not workitem.entity.dxf_file and not workitem.entity.pdf_main %}
|
||||
<div class="text-muted">—</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if workitem.entity.entity_type == 'product' or workitem.entity.entity_type == 'assembly' %}
|
||||
<div class="mb-4">
|
||||
<div class="fw-bold mb-2">Паспорт сборки</div>
|
||||
<div class="row g-2">
|
||||
<div class="col-md-4">
|
||||
<div class="small text-muted">Масса, кг</div>
|
||||
<div>{{ passport.weight_kg|default:"—" }}</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="small text-muted">Покрытие</div>
|
||||
<div>{{ passport.coating|default:"—" }}</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="small text-muted">Цвет</div>
|
||||
<div>{{ passport.coating_color|default:"—" }}</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="small text-muted">Площадь покрытия, м²</div>
|
||||
<div>{{ passport.coating_area_m2|default:"—" }}</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="small text-muted">Сварка</div>
|
||||
<div>{% if passport.requires_welding %}Да{% else %}Нет{% endif %}</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="small text-muted">Покраска</div>
|
||||
<div>{% if passport.requires_painting %}Да{% else %}Нет{% endif %}</div>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<div class="small text-muted">Технические требования</div>
|
||||
<div class="border border-secondary rounded p-2 bg-body">{{ passport.technical_requirements|default:"—"|linebreaksbr }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<div class="fw-bold mb-2">Сварочные швы</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Наименование</th>
|
||||
<th class="text-center" style="width:120px;">Катет, мм</th>
|
||||
<th class="text-center" style="width:120px;">Длина, мм</th>
|
||||
<th class="text-center" style="width:120px;">Кол-во</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for s in welding_seams %}
|
||||
<tr>
|
||||
<td>{{ s.name }}</td>
|
||||
<td class="text-center">{{ s.leg_mm }}</td>
|
||||
<td class="text-center">{{ s.length_mm }}</td>
|
||||
<td class="text-center">{{ s.quantity }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="4" class="text-center text-muted py-3">Швов нет</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="d-flex justify-content-between mt-4">
|
||||
<a href="{{ back_url }}" class="btn btn-outline-secondary">Назад</a>
|
||||
{% if user_role in 'admin,technologist,master,operator' %}
|
||||
<button type="button" class="btn btn-outline-accent px-4 fw-bold" onclick="document.getElementById('workitemForm')?.requestSubmit()">
|
||||
<i class="bi bi-save me-2"></i>Сохранить
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const done = document.getElementById('wiDone');
|
||||
const form = document.getElementById('workitemForm');
|
||||
if (done) {
|
||||
done.focus({ preventScroll: true });
|
||||
done.select();
|
||||
done.addEventListener('keydown', function (e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (form) form.requestSubmit();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
29
shiftflow/templates/shiftflow/workitem_entity_list.html
Normal file
29
shiftflow/templates/shiftflow/workitem_entity_list.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% 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-list-task me-2"></i>{{ entity.drawing_number|default:"—" }} {{ entity.name }}
|
||||
</h3>
|
||||
<div class="small text-muted">
|
||||
Сделка {{ deal.number }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
{% if can_edit_entity %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'product_info' entity.id %}?next={{ request.path|urlencode }}">
|
||||
<i class="bi bi-pencil-square me-1"></i>Паспорт
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ back_url }}">
|
||||
<i class="bi bi-arrow-left me-1"></i>Назад
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'shiftflow/partials/_workitems_table.html' with workitems=workitems %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
117
shiftflow/templates/shiftflow/workitem_kitting.html
Normal file
117
shiftflow/templates/shiftflow/workitem_kitting.html
Normal file
@@ -0,0 +1,117 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<div>
|
||||
<h3 class="text-accent mb-1">
|
||||
<i class="bi bi-box-seam me-2"></i>Комплектация
|
||||
</h3>
|
||||
<div class="small text-muted">
|
||||
Сделка № {{ workitem.deal.number }} · {{ workitem.entity.drawing_number|default:"—" }} {{ workitem.entity.name }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{{ back_url }}">
|
||||
<i class="bi bi-arrow-left me-1"></i>Назад
|
||||
</a>
|
||||
{% if draft %}
|
||||
<a class="btn btn-outline-accent btn-sm" href="{% url 'workitem_kitting_print' workitem.id %}" target="_blank" rel="noopener">
|
||||
<i class="bi bi-printer me-1"></i>Печать
|
||||
</a>
|
||||
<form method="post" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="action" value="clear">
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||||
<button class="btn btn-outline-secondary btn-sm" type="submit">Очистить лист</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="card shadow-sm border-secondary">
|
||||
<div class="card-header border-secondary d-flex justify-content-between align-items-center">
|
||||
<div class="fw-bold">Потребные компоненты</div>
|
||||
<div class="small text-muted">
|
||||
{% if to_location %}
|
||||
Куда: {{ to_location.name }} · Кол-во: {{ qty_to_make }} шт
|
||||
{% else %}
|
||||
Склад участка не определён
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th>Компонент</th>
|
||||
<th class="text-center" style="width:110px;">Нужно</th>
|
||||
<th class="text-center" style="width:130px;">Есть на участке</th>
|
||||
<th class="text-center" style="width:130px;">К перемещению</th>
|
||||
<th class="text-center" style="width:110px;">Не хватает</th>
|
||||
<th style="width:380px;">Откуда взять</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in rows %}
|
||||
<tr>
|
||||
<td>
|
||||
<div class="fw-bold">{{ r.entity.drawing_number|default:"—" }} {{ r.entity.name }}</div>
|
||||
<div class="small text-muted">{{ r.entity.get_entity_type_display }}</div>
|
||||
</td>
|
||||
<td class="text-center">{{ r.need }}</td>
|
||||
<td class="text-center">{{ r.have_to }}</td>
|
||||
<td class="text-center">{{ r.to_move }}</td>
|
||||
<td class="text-center">
|
||||
{% if r.missing > 0 %}
|
||||
<span class="text-danger fw-bold">{{ r.missing }}</span>
|
||||
{% else %}
|
||||
<span class="text-success">0</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.sources %}
|
||||
<div class="d-grid gap-2">
|
||||
{% for s in r.sources %}
|
||||
<form method="post" class="border border-secondary rounded p-2">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="entity_id" value="{{ r.entity.id }}">
|
||||
<input type="hidden" name="from_location_id" value="{{ s.location.id }}">
|
||||
<input type="hidden" name="next" value="{{ request.get_full_path }}">
|
||||
<div class="d-flex justify-content-between align-items-center">
|
||||
<div>
|
||||
<div class="fw-bold">{{ s.location.name }}</div>
|
||||
<div class="small text-muted">Доступно: {{ s.available }}{% if s.selected %} · В перемещении: {{ s.selected }}{% endif %}</div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<input class="form-control form-control-sm border-secondary" style="width:110px;" type="number" min="1" name="quantity" value="{% if r.missing > 0 %}{{ r.missing }}{% else %}1{% endif %}">
|
||||
<button class="btn btn-outline-accent btn-sm" type="submit" name="action" value="add_line">В перемещение</button>
|
||||
<button class="btn btn-outline-secondary btn-sm" type="submit" name="action" value="remove_line" {% if not s.selected %}disabled{% endif %}>Откатить</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="text-muted">Нет остатков (под сделку/свободных)</div>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="6" class="text-center text-muted py-4">Нет потребных компонентов (или кол-во = 0)</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card-footer border-secondary small text-muted">
|
||||
В таблице показываются только остатки под эту сделку и свободные. «Не хватает» пересчитывается с учетом колонки «К перемещению».
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
75
shiftflow/templates/shiftflow/workitem_kitting_print.html
Normal file
75
shiftflow/templates/shiftflow/workitem_kitting_print.html
Normal file
@@ -0,0 +1,75 @@
|
||||
<!doctype html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Перемещение</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; background: #fff; color: #000; }
|
||||
.page { width: 210mm; margin: 10mm auto; }
|
||||
.actions { display: flex; gap: 8px; justify-content: flex-end; margin-bottom: 8px; }
|
||||
.btn { border: 1px solid #000; background: #fff; padding: 6px 10px; font-size: 12px; cursor: pointer; }
|
||||
h1 { font-size: 18px; margin: 0 0 6px 0; }
|
||||
.meta { font-size: 12px; margin: 0 0 12px 0; }
|
||||
.block-title { font-weight: 700; margin: 14px 0 6px; }
|
||||
table { width: 100%; border-collapse: collapse; }
|
||||
th, td { border: 1px solid #000; padding: 6px 8px; font-size: 12px; }
|
||||
th { text-align: center; font-weight: 700; }
|
||||
td.num, th.num { text-align: right; width: 14mm; }
|
||||
td.qty, th.qty { text-align: right; width: 30mm; }
|
||||
td.note, th.note { width: 45mm; }
|
||||
td.name { text-align: left; }
|
||||
@media print { .actions { display:none; } .page { margin: 0; width: auto; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page">
|
||||
<div class="actions">
|
||||
<button type="button" class="btn" onclick="window.close()">Закрыть</button>
|
||||
<form method="post" style="margin:0">
|
||||
{% csrf_token %}
|
||||
<button type="submit" name="action" value="apply" class="btn">Принять</button>
|
||||
</form>
|
||||
<form method="post" style="margin:0">
|
||||
{% csrf_token %}
|
||||
<button type="submit" name="action" value="apply_print" class="btn">Распечатать</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<h1>Перемещение на {{ to_location.name }}</h1>
|
||||
<div class="meta">Сделка № {{ workitem.deal.number }} · {{ workitem.entity.drawing_number|default:"—" }} {{ workitem.entity.name }} · {{ printed_at|date:"d.m.Y H:i" }}</div>
|
||||
|
||||
{% for g in groups %}
|
||||
<div class="block-title">С {{ g.from_location.name }}</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="num">№</th>
|
||||
<th>Наименование</th>
|
||||
<th class="qty">К перемещению, шт</th>
|
||||
<th class="note">Отметка</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for it in g.items %}
|
||||
<tr>
|
||||
<td class="num">{{ forloop.counter }}</td>
|
||||
<td class="name">{{ it.entity.drawing_number|default:"—" }} {{ it.entity.name }}</td>
|
||||
<td class="qty">{{ it.quantity }}</td>
|
||||
<td class="note"></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% empty %}
|
||||
<div class="meta">Лист перемещения пуст.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if auto_print %}
|
||||
<script>
|
||||
window.print();
|
||||
</script>
|
||||
{% endif %}
|
||||
</body>
|
||||
</html>
|
||||
47
shiftflow/templates/shiftflow/workitem_op_closing.html
Normal file
47
shiftflow/templates/shiftflow/workitem_op_closing.html
Normal file
@@ -0,0 +1,47 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm border-secondary mb-4">
|
||||
<div class="card-header border-secondary d-flex justify-content-between align-items-center py-3">
|
||||
<h3 class="text-accent mb-0">
|
||||
<i class="bi bi-check2-square me-2"></i>Закрытие операции
|
||||
</h3>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'workitem_detail' workitem.id %}">Назад к заданию</a>
|
||||
</div>
|
||||
|
||||
<div class="card-body p-4">
|
||||
<div class="mb-3">
|
||||
<div class="fw-bold">{{ workitem.entity.drawing_number|default:"—" }} {{ workitem.entity.name }}</div>
|
||||
<div class="small text-muted">Сделка № {{ workitem.deal.number }}</div>
|
||||
<div class="small text-muted">
|
||||
Операция: {% if workitem.operation %}{{ workitem.operation.name }}{% else %}{{ workitem.stage|default:"—" }}{% endif %}
|
||||
</div>
|
||||
<div class="small text-muted">План: {{ workitem.quantity_plan }} · Факт: {{ workitem.quantity_done }} · Остаток: <strong>{{ remaining }}</strong></div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info border-info">
|
||||
Эта операция не списывает сырьё/комплектующие. Здесь фиксируется только факт выполнения.
|
||||
</div>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
<div class="row align-items-end g-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label text-muted small mb-1">Фактически выполнено (шт.)</label>
|
||||
<input type="number" class="form-control border-secondary" name="fact_qty" min="1" max="{{ remaining }}" value="{{ remaining }}" {% if remaining == 0 %}disabled{% endif %}>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<button type="submit" class="btn btn-warning w-100" {% if remaining == 0 %}disabled{% endif %}>
|
||||
Закрыть операцию
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
43
shiftflow/templates/shiftflow/workshops_catalog.html
Normal file
43
shiftflow/templates/shiftflow/workshops_catalog.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card shadow border-secondary mb-3">
|
||||
<div class="card-header border-secondary py-3 d-flex flex-wrap gap-2 justify-content-between align-items-center">
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-building me-2"></i>Справочник · Цеха</h3>
|
||||
<a class="btn btn-outline-secondary btn-sm" href="{% url 'directories' %}">Назад</a>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle" data-sortable="1">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th class="text-center" style="width:80px;" data-sort-type="number">№</th>
|
||||
<th class="text-center" style="width:90px;" data-sort-type="number">ID</th>
|
||||
<th>Цех</th>
|
||||
<th>Склад цеха</th>
|
||||
<th>Посты/станки</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for ws in workshops %}
|
||||
<tr style="cursor:pointer" onclick="window.location.href='{% url 'machines_catalog' %}?workshop_id={{ ws.id }}';">
|
||||
<td class="text-center">{{ forloop.counter }}</td>
|
||||
<td class="text-center text-muted">{{ ws.id }}</td>
|
||||
<td class="fw-bold">{{ ws.name }}</td>
|
||||
<td>{{ ws.location.name|default:"—" }}</td>
|
||||
<td class="small">
|
||||
{% if ws.machine_labels %}
|
||||
{{ ws.machine_labels }}
|
||||
{% else %}
|
||||
<span class="text-muted">—</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="5" class="text-center text-muted py-4">Цехов нет.</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -32,14 +32,30 @@
|
||||
<div class="small text-muted">По производственным отчетам</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body">
|
||||
{% for card in report_cards %}
|
||||
<form method="post" class="mb-0">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="start_date" value="{{ start_date }}">
|
||||
<input type="hidden" name="end_date" value="{{ end_date }}">
|
||||
|
||||
<div class="card-body">
|
||||
{% for card in report_cards %}
|
||||
<div class="border border-secondary rounded p-3 mb-3">
|
||||
<div class="d-flex flex-wrap justify-content-between gap-2">
|
||||
<div class="d-flex flex-wrap justify-content-between gap-2 align-items-center">
|
||||
<div class="fw-bold">
|
||||
{{ card.report.date|date:"d.m.Y" }} — {{ card.report.machine }} — {{ card.report.operator }}
|
||||
<span class="text-muted small ms-2">#{{ card.report.id }}</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
{% if card.report.is_synced_1c %}
|
||||
<span class="badge bg-success">Выгружено в 1С</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Не выгружено</span>
|
||||
{% if can_edit %}
|
||||
<input class="form-check-input" type="checkbox" name="report_ids" value="{{ card.report.id }}" title="Отметить выгружено в 1С">
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mt-1">
|
||||
@@ -54,6 +70,12 @@
|
||||
({% if c.stock_item.current_length and c.stock_item.current_width %}{{ c.stock_item.current_length|floatformat:"-g" }}×{{ c.stock_item.current_width|floatformat:"-g" }}{% elif c.stock_item.current_length %}{{ c.stock_item.current_length|floatformat:"-g" }}{% else %}—{% endif %})
|
||||
{{ c.quantity|floatformat:"-g" }} шт
|
||||
</li>
|
||||
{% elif c.stock_item_id and c.stock_item.entity_id %}
|
||||
<li>
|
||||
{{ c.stock_item.entity }}
|
||||
{% if c.stock_item.deal_id %}<span class="text-muted">(сделка № {{ c.stock_item.deal.number }})</span>{% endif %}
|
||||
{{ c.quantity|floatformat:"-g" }} шт
|
||||
</li>
|
||||
{% elif c.material_id %}
|
||||
<li>{{ c.material }} {{ c.quantity|floatformat:"-g" }} шт</li>
|
||||
{% else %}
|
||||
@@ -81,10 +103,14 @@
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="small text-muted fw-bold mb-1">Остаток ДО</div>
|
||||
{% if card.remnants %}
|
||||
{% if card.report.remnants.all %}
|
||||
<ul class="mb-0">
|
||||
{% for k,v in card.remnants.items %}
|
||||
<li>{{ k }}: {{ v }} шт</li>
|
||||
{% for r in card.report.remnants.all %}
|
||||
<li>
|
||||
{{ r.material.full_name|default:r.material.name|default:r.material }}
|
||||
({% if r.current_length and r.current_width %}{{ r.current_length|floatformat:"-g" }}×{{ r.current_width|floatformat:"-g" }}{% elif r.current_length %}{{ r.current_length|floatformat:"-g" }}{% else %}—{% endif %})
|
||||
{{ r.quantity|floatformat:"-g" }} шт
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
@@ -93,79 +119,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="text-muted">За выбранный период отчётов нет.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow border-secondary">
|
||||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||||
<h3 class="text-accent mb-0"><i class="bi bi-check2-square me-2"></i>Сменные задания (1С)</h3>
|
||||
<div class="small text-muted">Отметка «Списано в 1С»</div>
|
||||
</div>
|
||||
|
||||
<form method="post" class="mb-0">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="start_date" value="{{ start_date }}">
|
||||
<input type="hidden" name="end_date" value="{{ end_date }}">
|
||||
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0 align-middle">
|
||||
<thead>
|
||||
<tr class="table-custom-header">
|
||||
<th data-sort="false"></th>
|
||||
<th>Дата</th>
|
||||
<th>Сделка</th>
|
||||
<th>Станок</th>
|
||||
<th>Деталь</th>
|
||||
<th>Статус</th>
|
||||
<th>Факт</th>
|
||||
<th>1С</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for it in items %}
|
||||
<tr>
|
||||
<td style="width:40px;">
|
||||
{% if can_edit and not it.is_synced_1c %}
|
||||
<input class="form-check-input" type="checkbox" name="item_ids" value="{{ it.id }}">
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="small">{{ it.date|date:"d.m.Y" }}</td>
|
||||
<td>
|
||||
{% if it.task.deal_id %}
|
||||
<span class="text-accent fw-bold">{{ it.task.deal.number }}</span>
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ it.machine }}</td>
|
||||
<td>
|
||||
<a href="{% url 'item_detail' it.id %}" class="text-decoration-none">{{ it.task.drawing_name }}</a>
|
||||
</td>
|
||||
<td>{{ it.get_status_display }}</td>
|
||||
<td>{{ it.quantity_fact }}</td>
|
||||
<td>
|
||||
{% if it.is_synced_1c %}
|
||||
<span class="badge bg-success">Да</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary">Нет</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr><td colspan="8" class="text-center text-muted py-4">Нет закрытых заданий за период</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% empty %}
|
||||
<div class="text-muted">За выбранный период отчётов нет.</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
{% if can_edit %}
|
||||
<div class="card-footer border-secondary d-flex justify-content-end">
|
||||
<button type="submit" class="btn btn-outline-accent" {% if not can_edit %}disabled{% endif %}>
|
||||
Отметить выбранные как «Списано в 1С»
|
||||
</button>
|
||||
<button type="submit" class="btn btn-outline-accent">Отметить выбранные как «Выгружено в 1С»</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user