All checks were successful
Deploy MES Core / deploy (push) Successful in 10s
212 lines
10 KiB
HTML
212 lines
10 KiB
HTML
{% extends 'base.html' %}
|
||
|
||
{% block content %}
|
||
<div class="card shadow border-secondary mb-3">
|
||
<div class="card-header border-secondary py-3 d-flex justify-content-between align-items-center">
|
||
<div>
|
||
<h3 class="text-accent mb-1">
|
||
<i class="bi bi-briefcase me-2"></i>Сделка {{ deal.number }}
|
||
</h3>
|
||
<div class="small text-muted">
|
||
{% if deal.company %}{{ deal.company.name }}{% else %}—{% endif %}
|
||
{% if deal.description %} · {{ deal.description }}{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="d-flex gap-2">
|
||
<span class="badge {% if deal.status == 'work' %}bg-primary{% elif deal.status == 'done' %}bg-success{% else %}bg-secondary{% endif %} align-self-center">
|
||
{{ deal.get_status_display }}
|
||
</span>
|
||
<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,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>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-body p-0">
|
||
<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 style="width: 160px;">Прогресс</th>
|
||
<th class="text-center">Надо / Сделано / В плане</th>
|
||
<th class="text-center">Осталось</th>
|
||
<th class="text-center">Файлы</th>
|
||
<th 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>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="modal fade" id="addToPlanModal" tabindex="-1" aria-hidden="true">
|
||
<div class="modal-dialog">
|
||
<form method="post" action="{% url 'planning_add' %}" class="modal-content border-secondary">
|
||
{% csrf_token %}
|
||
<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="task_id" id="modalTaskId">
|
||
<div class="small text-muted mb-2" id="modalTaskTitle"></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">
|
||
{% for m in machines %}
|
||
<input type="radio" class="btn-check" name="machine_id" id="m_{{ m.id }}" value="{{ m.id }}" required>
|
||
<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>
|
||
</div>
|
||
|
||
<div class="small text-muted" id="modalHint"></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', function () {
|
||
document.querySelectorAll('tr.task-row[data-href]').forEach(function (row) {
|
||
row.addEventListener('click', function (e) {
|
||
if (e.target && (e.target.closest('button') || e.target.closest('.stop-prop'))) return;
|
||
const href = row.getAttribute('data-href');
|
||
if (href) window.location.href = href;
|
||
});
|
||
});
|
||
|
||
document.querySelectorAll('.sf-progress').forEach(function (el) {
|
||
const done = parseInt(el.getAttribute('data-done-width') || '0', 10) || 0;
|
||
const plan = parseInt(el.getAttribute('data-plan-width') || '0', 10) || 0;
|
||
const doneEl = el.querySelector('.sf-progress-done');
|
||
const planEl = el.querySelector('.sf-progress-plan');
|
||
if (doneEl) doneEl.style.width = `${done}%`;
|
||
if (planEl) planEl.style.width = `${plan}%`;
|
||
});
|
||
|
||
const modal = document.getElementById('addToPlanModal');
|
||
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 rem = btn.getAttribute('data-task-rem');
|
||
|
||
document.getElementById('modalTaskId').value = taskId;
|
||
document.getElementById('modalTaskTitle').textContent = name;
|
||
document.getElementById('modalHint').textContent = rem !== null ? `Осталось: ${rem} шт` : '';
|
||
|
||
const qty = document.getElementById('modalQty');
|
||
qty.value = '';
|
||
|
||
let remInt = null;
|
||
if (rem && !isNaN(parseInt(rem, 10))) {
|
||
remInt = Math.max(1, parseInt(rem, 10));
|
||
qty.max = remInt;
|
||
qty.value = String(remInt);
|
||
} else {
|
||
qty.removeAttribute('max');
|
||
}
|
||
|
||
qty.focus({ preventScroll: true });
|
||
qty.select();
|
||
|
||
qty.onkeydown = function (e) {
|
||
if (e.key === 'Enter') {
|
||
e.preventDefault();
|
||
const form = document.querySelector('#addToPlanModal form');
|
||
if (form) form.requestSubmit();
|
||
}
|
||
};
|
||
|
||
const radios = Array.from(document.querySelectorAll('input[name="machine_id"]'));
|
||
const savedMachine = (() => { try { return localStorage.getItem('planning_machine_id'); } catch (_) { return null; } })();
|
||
|
||
let selected = null;
|
||
if (savedMachine) {
|
||
selected = radios.find(r => r.value === savedMachine);
|
||
}
|
||
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 (_) {}
|
||
};
|
||
});
|
||
});
|
||
});
|
||
</script>
|
||
{% endblock %} |