Files
MES_Core/shiftflow/templates/shiftflow/warehouse_stocks.html
ackFromRedmi 69edd3fa97
All checks were successful
Deploy MES Core / deploy (push) Successful in 11s
Переезд на схему нового доступа
2026-04-13 08:26:07 +03:00

565 lines
27 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{% extends 'base.html' %}
{% block content %}
<div class="card border-secondary mb-3 shadow-sm">
<div class="card-body py-2">
<form method="get" id="warehouse-filter-form" class="row g-2 align-items-center">
<input type="hidden" name="q" value="{{ q }}">
<div class="col-md-7">
<div class="small text-muted mb-1 fw-bold">Склады:</div>
<div class="d-flex flex-wrap gap-1">
<div>
<input type="radio" class="btn-check" name="location_id" id="wl_all" value="" {% if not selected_location_id %}checked{% endif %} onchange="this.form.submit()">
<label class="btn btn-outline-accent btn-sm" for="wl_all">Все</label>
</div>
{% for loc in locations %}
<div>
<input type="radio" class="btn-check" name="location_id" id="wl_{{ loc.id }}" value="{{ loc.id }}" {% if selected_location_id == loc.id|stringformat:"s" %}checked{% endif %} onchange="this.form.submit()">
<label class="btn btn-outline-accent btn-sm" for="wl_{{ loc.id }}">{{ loc }}</label>
</div>
{% endfor %}
</div>
</div>
<div class="col-md-4">
<div class="small text-muted mb-1 fw-bold">Тип:</div>
<div class="d-flex flex-wrap gap-1">
<div>
<input type="radio" class="btn-check" name="kind" id="wk_all" value="" {% if not selected_kind %}checked{% endif %} onchange="this.form.submit()">
<label class="btn btn-outline-accent btn-sm" for="wk_all">Все</label>
</div>
<div>
<input type="radio" class="btn-check" name="kind" id="wk_raw" value="raw" {% if selected_kind == 'raw' %}checked{% endif %} onchange="this.form.submit()">
<label class="btn btn-outline-primary btn-sm" for="wk_raw">Сырьё</label>
</div>
<div>
<input type="radio" class="btn-check" name="kind" id="wk_finished" value="finished" {% if selected_kind == 'finished' %}checked{% endif %} onchange="this.form.submit()">
<label class="btn btn-outline-success btn-sm" for="wk_finished">Изделия</label>
</div>
</div>
</div>
<div class="col-md-auto">
<div class="small text-muted mb-1 fw-bold">Период:</div>
<div class="d-flex gap-2">
<input type="date" name="start_date" class="form-control form-control-sm bg-body text-body border-secondary" value="{{ start_date }}" onchange="this.form.submit()">
<input type="date" name="end_date" class="form-control form-control-sm bg-body text-body border-secondary" value="{{ end_date }}" onchange="this.form.submit()">
</div>
</div>
<div class="col-md-1 text-end mt-auto">
<a href="{% url 'warehouse_stocks' %}?reset=1" class="btn btn-outline-secondary btn-sm w-100" title="Сброс">
<i class="bi bi-arrow-counterclockwise"></i>
</a>
</div>
</form>
</div>
</div>
<div class="card shadow border-secondary">
<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-box-seam me-2"></i>Склады</h3>
<div class="d-flex flex-wrap gap-2 align-items-center">
{% if can_receive %}
<button type="button" class="btn btn-outline-accent btn-sm" data-bs-toggle="modal" data-bs-target="#receiptModal">
<i class="bi bi-box-arrow-in-down me-1"></i>Приход
</button>
{% endif %}
<form class="d-flex gap-2 align-items-center" method="get" action="{% url 'warehouse_stocks' %}">
<input type="hidden" name="location_id" value="{{ selected_location_id }}">
<input type="hidden" name="kind" value="{{ selected_kind }}">
<input type="hidden" name="start_date" value="{{ start_date }}">
<input type="hidden" name="end_date" value="{{ end_date }}">
<input class="form-control form-control-sm" name="q" value="{{ q }}" placeholder="Поиск (материал, деталь, склад, ID)" style="min-width: 360px;">
<button class="btn btn-outline-secondary btn-sm" type="submit"><i class="bi bi-search me-1"></i>Найти</button>
</form>
</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-type="date">Поступление</th>
<th>Сделка</th>
<th>Наименование</th>
<th>Тип</th>
<th data-sort-type="number">Кол-во</th>
<th>Ед. измерения</th>
<th>ДО</th>
<th data-sort="false">Действия</th>
</tr>
</thead>
<tbody>
{% for it in items %}
<tr>
<td>{{ it.location }}</td>
<td>{% if it.created_at %}{{ it.created_at|date:"d.m.Y H:i" }}{% endif %}</td>
<td>
{% if it.deal_id %}
<span class="text-accent fw-bold">{{ it.deal.number }}</span>
{% else %}
{% endif %}
</td>
<td>
{% if it.material_id %}
{{ it.material.full_name }}
{% if it.current_length and it.current_width %}
<div class="small text-muted mt-1">({{ it.current_length|floatformat:"-g" }}×{{ it.current_width|floatformat:"-g" }} мм)</div>
{% elif it.current_length %}
<div class="small text-muted mt-1">({{ it.current_length|floatformat:"-g" }} мм)</div>
{% endif %}
{% elif it.entity_id %}
{{ it.entity }}
{% else %}
{% endif %}
{% if it.unique_id %}
<div class="small text-muted mt-1">{{ it.unique_id }}</div>
{% endif %}
</td>
<td>
{% if it.entity_id %}
Изделие/деталь
{% elif it.is_remnant %}
ДО
{% else %}
Сырьё
{% endif %}
</td>
<td>{{ it.quantity }}</td>
<td>
{% if it.entity_id %}
шт
{% elif it.material_id and it.material.category_id %}
{% with ff=it.material.category.form_factor|stringformat:"s"|lower %}
{% if ff == 'лист' or ff == 'sheet' %}лист
{% elif ff == 'прокат' or ff == 'rolled' or ff == 'roll' %}прокат
{% else %}ед.
{% endif %}
{% endwith %}
{% else %}
ед.
{% endif %}
</td>
<td>
{% if it.is_remnant %}Да{% else %}—{% endif %}
</td>
<td>
{% if can_transfer %}
<div class="d-flex gap-2">
<button
type="button"
class="btn btn-outline-accent btn-sm"
data-bs-toggle="modal"
data-bs-target="#transferModal"
data-mode="transfer"
data-stock-item-id="{{ it.id }}"
data-stock-item-name="{% if it.material_id %}{{ it.material.full_name }}{% elif it.entity_id %}{{ it.entity }}{% else %}—{% endif %}"
data-from-location="{{ it.location }}"
data-from-location-id="{{ it.location_id }}"
data-max="{{ it.quantity }}"
>
<i class="bi bi-arrow-left-right me-1"></i>Переместить
</button>
<button
type="button"
class="btn btn-outline-secondary btn-sm"
data-bs-toggle="modal"
data-bs-target="#transferModal"
data-mode="ship"
data-stock-item-id="{{ it.id }}"
data-stock-item-name="{% if it.material_id %}{{ it.material.full_name }}{% elif it.entity_id %}{{ it.entity }}{% else %}—{% endif %}"
data-from-location="{{ it.location }}"
data-from-location-id="{{ it.location_id }}"
data-max="{{ it.quantity }}"
>
<i class="bi bi-truck me-1"></i>Отгрузка
</button>
</div>
{% else %}
<span class="text-muted small">только просмотр</span>
{% endif %}
</td>
</tr>
{% empty %}
<tr><td colspan="8" class="text-center text-muted py-4">Нет позиций по текущим фильтрам</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="modal fade" id="receiptModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<form method="post" action="{% url 'warehouse_receipt' %}" 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">
<div class="row g-2">
<div class="col-md-4">
<label class="form-label">Тип</label>
<select class="form-select" name="kind" id="receiptKind" required>
<option value="raw">Сырьё / покупное</option>
<option value="entity">Изделие/деталь</option>
</select>
</div>
<div class="col-md-4">
<label class="form-label">Сделка</label>
<select class="form-select" name="deal_id">
<option value="">— не указано —</option>
{% for d in deals %}
<option value="{{ d.id }}">{{ d.number }}{% if d.company_id %} — {{ d.company.name }}{% endif %}{% if d.description %} — {{ d.description }}{% endif %}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label">Склад</label>
<select class="form-select" name="location_id" required>
{% for loc in receipt_locations %}
<option value="{{ loc.id }}">{{ loc }}</option>
{% endfor %}
</select>
</div>
<div class="col-12" id="receiptRawBlock">
<label class="form-label">Материал</label>
<select class="form-select" name="material_id" id="receiptMaterial">
{% for m in materials %}
<option value="{{ m.id }}" data-ff="{{ m.category.form_factor|default:'' }}">{{ m.full_name|default:m.name }}</option>
{% endfor %}
</select>
<div class="row g-2 mt-1">
<div class="col-md-4">
<label class="form-label">Кол-во</label>
<input class="form-control" name="quantity" id="receiptQtyRaw" placeholder="Напр. 1" required>
</div>
<div class="col-md-4">
<label class="form-label">Длина (мм)</label>
<input class="form-control" name="current_length" id="receiptLen" placeholder="Напр. 2500">
</div>
<div class="col-md-4">
<label class="form-label">Ширина (мм)</label>
<input class="form-control" name="current_width" id="receiptWid" placeholder="Напр. 1250">
</div>
<div class="col-12 mt-1">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="is_customer_supplied" id="receiptDav">
<label class="form-check-label" for="receiptDav">Давальческий</label>
</div>
</div>
</div>
</div>
<div class="col-12" id="receiptEntityBlock" style="display:none;">
<div class="row g-2">
<div class="col-md-8">
<label class="form-label">КД (изделие/деталь)</label>
<select class="form-select" name="entity_id">
{% for e in entities %}
<option value="{{ e.id }}">{{ e }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-4">
<label class="form-label">Кол-во</label>
<input class="form-control" name="quantity" id="receiptQtyEntity" placeholder="Напр. 1" required>
</div>
</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">Добавить</button>
</div>
</form>
</div>
</div>
<div class="modal fade" id="transferModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog modal-lg">
<form method="post" action="{% url 'warehouse_transfer' %}" class="modal-content border-secondary">
{% csrf_token %}
<input type="hidden" name="stock_item_id" id="transferStockItemId">
<input type="hidden" name="next" value="{{ request.get_full_path }}">
<div class="modal-header border-secondary">
<h5 class="modal-title" id="transferTitle">Перемещение</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Закрыть"></button>
</div>
<div class="modal-body">
<div class="mb-2">
<div class="fw-bold" id="transferInfoFrom"></div>
<div class="small text-muted" id="transferInfoName"></div>
<div class="small text-muted" id="transferInfoAvail"></div>
</div>
<div class="alert alert-danger d-none" id="transferError" role="alert"></div>
<div class="row g-2">
<div class="col-md-6" id="transferToCol">
<label class="form-label">Куда</label>
<select class="form-select" name="to_location_id" id="transferToLocation" required>
{% if shipping_location_id %}
<option value="{{ shipping_location_id }}" data-shipping="1" hidden disabled>{{ shipping_location_label|default:'Отгруженные позиции' }}</option>
{% endif %}
{% for loc in locations %}
<option value="{{ loc.id }}">{{ loc }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-6">
<label class="form-label">Количество</label>
<input class="form-control" name="quantity" id="transferQty" placeholder="Напр. 1 или 2.5" inputmode="decimal" autofocus required>
<div class="small text-muted mt-1" id="transferMaxHint"></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">Применить</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', () => {
const modal = document.getElementById('transferModal');
if (!modal) return;
const form = modal.querySelector('form');
const idInput = document.getElementById('transferStockItemId');
const title = document.getElementById('transferTitle');
const infoFrom = document.getElementById('transferInfoFrom');
const infoName = document.getElementById('transferInfoName');
const infoAvail = document.getElementById('transferInfoAvail');
const errBox = document.getElementById('transferError');
const qty = document.getElementById('transferQty');
const maxHint = document.getElementById('transferMaxHint');
const toSel = document.getElementById('transferToLocation');
const toCol = document.getElementById('transferToCol');
const receiptKind = document.getElementById('receiptKind');
const receiptRaw = document.getElementById('receiptRawBlock');
const receiptEntity = document.getElementById('receiptEntityBlock');
const receiptMaterial = document.getElementById('receiptMaterial');
const receiptLen = document.getElementById('receiptLen');
const receiptWid = document.getElementById('receiptWid');
const receiptQtyRaw = document.getElementById('receiptQtyRaw');
const receiptQtyEntity = document.getElementById('receiptQtyEntity');
let currentMax = null;
let currentMode = 'transfer';
function showErr(text) {
if (!errBox) return;
if (!text) {
errBox.classList.add('d-none');
errBox.textContent = '';
return;
}
errBox.textContent = text;
errBox.classList.remove('d-none');
}
function parseNumber(text) {
const s = (text || '').toString().trim().replace(',', '.');
const n = parseFloat(s);
return isNaN(n) ? null : n;
}
modal.addEventListener('show.bs.modal', (ev) => {
const btn = ev.relatedTarget;
if (!btn) return;
currentMode = btn.getAttribute('data-mode') || 'transfer';
const stockItemId = btn.getAttribute('data-stock-item-id') || '';
const name = btn.getAttribute('data-stock-item-name') || '';
const fromLoc = btn.getAttribute('data-from-location') || '';
const fromLocId = btn.getAttribute('data-from-location-id') || '';
const maxRaw = btn.getAttribute('data-max') || '';
currentMax = parseNumber(maxRaw);
showErr('');
if (idInput) idInput.value = stockItemId;
if (title) title.textContent = currentMode === 'ship' ? 'Отгрузка' : 'Перемещение';
if (infoFrom) infoFrom.textContent = `Откуда: ${fromLoc}`;
if (infoName) infoName.textContent = `Что: ${name}`;
if (infoAvail) infoAvail.textContent = currentMax !== null ? `Доступно: ${currentMax}` : '';
if (maxHint) maxHint.textContent = currentMax !== null ? `Доступно: ${currentMax}` : '';
if (qty) {
qty.value = currentMax !== null ? String(currentMax) : '';
if (currentMax !== null) qty.setAttribute('max', String(currentMax));
qty.setAttribute('min', '0');
qty.setAttribute('step', 'any');
}
if (toSel) {
const shipId = '{{ shipping_location_id }}';
Array.from(toSel.options).forEach(opt => {
const isShipping = opt.getAttribute('data-shipping') === '1';
if (isShipping) {
if (currentMode === 'ship') {
opt.disabled = false;
opt.hidden = false;
} else {
opt.disabled = true;
opt.hidden = true;
}
return;
}
if (fromLocId && String(opt.value) === String(fromLocId)) {
opt.disabled = true;
opt.hidden = true;
} else {
opt.disabled = false;
opt.hidden = false;
}
});
const col = toCol || (toSel ? toSel.closest('.col-md-6') : null);
if (currentMode === 'ship') {
if (shipId) {
toSel.value = shipId;
}
if (col) col.style.display = 'none';
} else {
if (col) col.style.display = '';
const first = Array.from(toSel.options).find(o => !o.disabled && !o.hidden);
if (first) toSel.value = first.value;
}
}
});
modal.addEventListener('shown.bs.modal', () => {
if (qty) {
qty.focus();
qty.select();
}
});
if (form) {
form.addEventListener('submit', (e) => {
const v = parseNumber(qty ? qty.value : '');
if (v === null || v <= 0) {
e.preventDefault();
showErr('Количество должно быть больше 0.');
if (qty) {
qty.focus();
qty.select();
}
return;
}
if (currentMax !== null && v > currentMax) {
e.preventDefault();
showErr('Нельзя переместить больше, чем доступно.');
if (qty) {
qty.focus();
qty.select();
}
return;
}
if (currentMode === 'ship') {
const shipId = '{{ shipping_location_id }}';
if (!shipId) {
e.preventDefault();
showErr('Не найден склад отгруженных позиций. Создай склад с названием содержащим "отгруж" или "отгруз".');
return;
}
}
showErr('');
});
}
function syncReceiptKind() {
if (!receiptKind || !receiptRaw || !receiptEntity) return;
const isRaw = (receiptKind.value || '') === 'raw';
receiptRaw.style.display = isRaw ? '' : 'none';
receiptEntity.style.display = isRaw ? 'none' : '';
if (receiptQtyRaw) {
receiptQtyRaw.disabled = !isRaw;
receiptQtyRaw.required = isRaw;
}
if (receiptQtyEntity) {
receiptQtyEntity.disabled = isRaw;
receiptQtyEntity.required = !isRaw;
}
}
function applyReceiptDefaults() {
if (!receiptMaterial) return;
const opt = receiptMaterial.options[receiptMaterial.selectedIndex];
const ff = (opt && opt.getAttribute('data-ff') || '').toLowerCase();
if (ff === 'sheet') {
if (receiptLen && !receiptLen.value) receiptLen.value = '2500';
if (receiptWid && !receiptWid.value) receiptWid.value = '1250';
if (receiptWid) receiptWid.disabled = false;
} else if (ff === 'bar') {
if (receiptLen && !receiptLen.value) receiptLen.value = '6000';
if (receiptWid) {
receiptWid.value = '';
receiptWid.disabled = true;
}
} else {
if (receiptWid) receiptWid.disabled = false;
}
}
if (receiptKind) {
receiptKind.addEventListener('change', () => {
syncReceiptKind();
if (receiptKind.value === 'raw') {
if (receiptQtyRaw) {
receiptQtyRaw.focus();
receiptQtyRaw.select();
}
} else {
if (receiptQtyEntity) {
receiptQtyEntity.focus();
receiptQtyEntity.select();
}
}
});
syncReceiptKind();
}
if (receiptMaterial) {
receiptMaterial.addEventListener('change', applyReceiptDefaults);
applyReceiptDefaults();
}
});
</script>
{% endblock %}