Сортировка в таблицах и попытка приструнить генерацию превьюшек
All checks were successful
Deploy MES Core / deploy (push) Successful in 13s

This commit is contained in:
2026-04-03 01:10:05 +03:00
parent cddbfeadde
commit b76ce4913f
16 changed files with 722 additions and 34 deletions

View File

@@ -38,7 +38,7 @@
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<table class="table table-hover mb-0 align-middle" data-sortable="1">
<thead>
<tr class="table-custom-header">
<th>Сделка</th>

View File

@@ -13,7 +13,7 @@
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<table class="table table-hover mb-0 align-middle" data-sortable="1">
<thead>
<tr class="table-custom-header">
<th>Заказчик</th>

View File

@@ -17,6 +17,13 @@
<strong>DXF</strong>
</div>
<div class="card-body">
<div class="mb-3" id="jobBox" {% if not last_job %}style="display:none"{% endif %}>
<div class="small text-muted">Статус: <span id="jobStatus">{% if last_job %}{{ last_job.get_status_display }}{% endif %}</span></div>
<div class="small text-muted">Обработано: <span id="jobProcessed">{% if last_job %}{{ last_job.processed }}{% endif %}</span>/<span id="jobTotal">{% if last_job %}{{ last_job.total }}{% endif %}</span></div>
<div class="small text-muted">Обновлено: <span id="jobUpdated">{% if last_job %}{{ last_job.updated }}{% endif %}</span> · Пропущено: <span id="jobSkipped">{% if last_job %}{{ last_job.skipped }}{% endif %}</span> · Ошибок: <span id="jobErrors">{% if last_job %}{{ last_job.errors }}{% endif %}</span></div>
<div class="small text-muted" id="jobMessage">{% if last_job %}{{ last_job.last_message }}{% endif %}</div>
</div>
<form method="post" class="row g-3 align-items-end">
{% csrf_token %}
@@ -42,6 +49,11 @@
</div>
</div>
<div class="col-md-3">
<label class="small text-muted">Таймаут на 1 DXF (сек)</label>
<input type="number" step="1" min="5" name="per_task_timeout_sec" class="form-control border-secondary" value="{{ dxf_settings.per_task_timeout_sec|default_if_none:45|unlocalize }}">
</div>
<div class="col-12 d-flex gap-2">
<button type="submit" class="btn btn-outline-accent" name="action" value="save_settings">
<i class="bi bi-save me-2"></i>Сохранить настройки
@@ -49,6 +61,9 @@
<button type="submit" class="btn btn-outline-accent" name="action" value="update_previews">
<i class="bi bi-arrow-repeat me-2"></i>Обновить превьюшки DXF
</button>
<button type="submit" class="btn btn-outline-warning" name="action" value="cancel_job">
<i class="bi bi-stop-circle me-2"></i>Прервать
</button>
</div>
<div class="col-12">
@@ -67,6 +82,44 @@
{% endfor %}
</div>
{% endif %}
<script>
(function () {
const box = document.getElementById('jobBox');
const statusEl = document.getElementById('jobStatus');
const totalEl = document.getElementById('jobTotal');
const processedEl = document.getElementById('jobProcessed');
const updatedEl = document.getElementById('jobUpdated');
const skippedEl = document.getElementById('jobSkipped');
const errorsEl = document.getElementById('jobErrors');
const msgEl = document.getElementById('jobMessage');
async function tick() {
try {
const res = await fetch('{% url "maintenance_status" %}', { method: 'GET', headers: { 'Accept': 'application/json' } });
if (!res.ok) return;
const data = await res.json();
if (!data || !data.job) return;
if (box) box.style.display = '';
if (statusEl) statusEl.textContent = data.job.status_label || data.job.status || '';
if (totalEl) totalEl.textContent = String(data.job.total ?? '');
if (processedEl) processedEl.textContent = String(data.job.processed ?? '');
if (updatedEl) updatedEl.textContent = String(data.job.updated ?? '');
if (skippedEl) skippedEl.textContent = String(data.job.skipped ?? '');
if (errorsEl) errorsEl.textContent = String(data.job.errors ?? '');
if (msgEl) msgEl.textContent = data.job.last_message || '';
if (data.job.status === 'running' || data.job.status === 'queued') {
setTimeout(tick, 3000);
}
} catch (e) {
}
}
setTimeout(tick, 400);
})();
</script>
</div>
</div>
{% endblock %}

View File

@@ -1,18 +1,18 @@
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<table class="table table-hover mb-0 align-middle" data-sortable="1">
<thead>
<tr class="table-custom-header">
<th>Дата</th>
<th data-sort-type="date">Дата</th>
<th>Сделка</th>
<th>Станок</th>
<th>Наименование</th>
<th>Габариты</th>
<th style="width: 160px;">Прогресс</th>
<th>План / Факт</th>
<th data-sort-type="number">Габариты</th>
<th data-sort="false" style="width: 160px;">Прогресс</th>
<th data-sort-type="number">План / Факт</th>
<th>Материал</th>
<th class="text-center">Файлы</th>
<th class="text-center">1С</th>
<th data-sort="false" class="text-center">Файлы</th>
<th data-sort="false" class="text-center">1С</th>
<th>Статус</th>
</tr>
</thead>

View File

@@ -28,7 +28,7 @@
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<table class="table table-hover mb-0 align-middle" data-sortable="1">
<thead>
<tr class="table-custom-header">
<th>Сделка</th>

View File

@@ -30,17 +30,17 @@
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0 align-middle">
<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 style="width: 160px;">Прогресс</th>
<th data-sort="false" style="width: 160px;">Прогресс</th>
<th class="text-center">Надо / Сделано / В плане</th>
<th class="text-center">Осталось</th>
<th class="text-center">Файлы</th>
<th class="text-end">Действия</th>
<th data-sort="false" class="text-center">Файлы</th>
<th data-sort="false" class="text-end">Действия</th>
</tr>
</thead>
<tbody>

View File

@@ -312,6 +312,8 @@ document.addEventListener('DOMContentLoaded', function () {
const drawingFile = document.getElementById('id_drawing_file');
const extraDrawing = document.getElementById('id_extra_drawing');
const drawingName = document.getElementById('id_drawing_name');
const qtyOrdered = document.getElementById('id_quantity_ordered');
const sizeValue = document.getElementById('id_size_value');
const fillFromSource = document.getElementById('fillNameFromSource');
const fillFromPdf = document.getElementById('fillNameFromPdf');
@@ -378,12 +380,46 @@ document.addEventListener('DOMContentLoaded', function () {
if (fillFromPdf) fillFromPdf.disabled = !hasPdf;
}
function tryFillFromDxfFilename(filename) {
if (!filename) return false;
const isDxf = filename.toLowerCase().endsWith('.dxf');
if (!isDxf) return false;
// Поддерживаем паттерн в имени файла вида "-s2n45".
// На практике встречаются варианты: "s2n45", "-s2_n45", "_s2-n45" и т.п.
// Поэтому ищем "s<число> ... n<целое>" в любом месте имени, разрешая разделители между ними.
const base = filenameBase(filename);
const normalized = base.replace(/\s+/g, '');
const m = normalized.match(/s([0-9]+(?:[\.,][0-9]+)?)[^0-9a-zA-Z]*n([0-9]+)/);
if (!m) return false;
const s = (m[1] || '').replace(',', '.');
const n = (m[2] || '').trim();
// По твоей просьбе: перезатираем значения, даже если пользователь что-то уже вводил.
if (sizeValue) {
sizeValue.value = s;
sizeValue.dispatchEvent(new Event('input', { bubbles: true }));
}
if (qtyOrdered) {
qtyOrdered.value = n;
qtyOrdered.dispatchEvent(new Event('input', { bubbles: true }));
}
return true;
}
if (drawingFile) drawingFile.addEventListener('change', function () {
updateFileButtons();
if (!drawingName) return;
if (drawingName.value && drawingName.value.trim() !== '') return;
if (!drawingFile.files || drawingFile.files.length === 0) return;
drawingName.value = filenameBase(drawingFile.files[0].name);
const fname = drawingFile.files[0].name;
// По твоей просьбе: при выборе исходника перезаполняем «Наименование» из имени файла.
if (drawingName) drawingName.value = filenameBase(fname);
// Пытаемся распарсить «Размер» и «Требуется» из имени DXF.
tryFillFromDxfFilename(fname);
});
if (extraDrawing) extraDrawing.addEventListener('change', updateFileButtons);
updateFileButtons();
@@ -391,8 +427,12 @@ document.addEventListener('DOMContentLoaded', function () {
if (fillFromSource) {
fillFromSource.addEventListener('click', function () {
if (!drawingFile || !drawingFile.files || drawingFile.files.length === 0) return;
const base = filenameBase(drawingFile.files[0].name);
const fname = drawingFile.files[0].name;
const base = filenameBase(fname);
// Кнопка также перезаполняет значения из имени исходника.
if (drawingName) drawingName.value = base;
tryFillFromDxfFilename(fname);
});
}