Добавлена система трансформации товаров
Реализована полная система трансформации товаров (превращение одного товара в другой). Пример: белая гипсофила → крашеная гипсофила. Особенности реализации: - Резервирование входных товаров в статусе draft - FIFO списание входных товаров при проведении - Автоматический расчёт себестоимости выходных товаров - Возможность отмены как черновиков, так и проведённых трансформаций Модели (inventory/models.py): - Transformation: документ трансформации (draft/completed/cancelled) - TransformationInput: входные товары (списание) - TransformationOutput: выходные товары (оприходование) - Добавлен статус 'converted_to_transformation' в Reservation - Добавлен тип 'transformation' в DocumentCounter Бизнес-логика (inventory/services/transformation_service.py): - TransformationService с методами CRUD - Валидация наличия товаров - Автоматическая генерация номеров документов Сигналы (inventory/signals.py): - Автоматическое резервирование входных товаров - FIFO списание при проведении - Создание партий выходных товаров - Откат операций при отмене Интерфейс без Django Admin: - Список трансформаций (list.html) - Форма создания (form.html) - Детальный просмотр с добавлением товаров (detail.html) - Интеграция с компонентом поиска товаров - 8 views для полного CRUD + проведение/отмена Миграция: - 0003_alter_documentcounter_counter_type_and_more.py 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -160,6 +160,24 @@
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Трансформации -->
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<a href="{% url 'inventory:transformation-list' %}" class="card shadow-sm h-100 text-decoration-none">
|
||||
<div class="card-body p-3">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-purple bg-opacity-10 p-3 me-3">
|
||||
<i class="bi bi-arrow-repeat text-purple" style="font-size: 1.5rem;"></i>
|
||||
</div>
|
||||
<div class="flex-grow-1">
|
||||
<h6 class="mb-0 text-dark">Трансформации</h6>
|
||||
<small class="text-muted">Превращение товаров</small>
|
||||
</div>
|
||||
<i class="bi bi-chevron-right text-muted"></i>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -293,5 +311,13 @@
|
||||
.card-body {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.bg-purple {
|
||||
background-color: #6f42c1 !important;
|
||||
}
|
||||
|
||||
.text-purple {
|
||||
color: #6f42c1 !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load static %}
|
||||
{% load inventory_filters %}
|
||||
|
||||
{% block title %}Трансформация {{ transformation.document_number }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- CSS для компонента поиска -->
|
||||
<link rel="stylesheet" href="{% static 'products/css/product-search-picker.css' %}">
|
||||
|
||||
<div class="container-fluid px-4 py-3">
|
||||
<!-- Breadcrumbs -->
|
||||
<nav aria-label="breadcrumb" class="mb-2">
|
||||
<ol class="breadcrumb breadcrumb-sm mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'inventory:transformation-list' %}">Трансформации</a></li>
|
||||
<li class="breadcrumb-item active">{{ transformation.document_number }}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<!-- Messages -->
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="row g-3">
|
||||
<!-- Основной контент - одна колонка -->
|
||||
<div class="col-12">
|
||||
<!-- Информация о трансформации -->
|
||||
<div class="card border-0 shadow-sm mb-3">
|
||||
<div class="card-header bg-light py-3 d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-arrow-repeat me-2"></i>{{ transformation.document_number }}
|
||||
{% if transformation.status == 'draft' %}
|
||||
<span class="badge bg-warning text-dark ms-2">Черновик</span>
|
||||
{% elif transformation.status == 'completed' %}
|
||||
<span class="badge bg-success ms-2">Проведён</span>
|
||||
{% elif transformation.status == 'cancelled' %}
|
||||
<span class="badge bg-secondary ms-2">Отменён</span>
|
||||
{% endif %}
|
||||
</h5>
|
||||
{% if transformation.status == 'draft' %}
|
||||
<div class="btn-group">
|
||||
<form method="post" action="{% url 'inventory:transformation-confirm' transformation.pk %}" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-success btn-sm" {% if not transformation.inputs.exists or not transformation.outputs.exists %}disabled{% endif %}>
|
||||
<i class="bi bi-check-lg me-1"></i>Провести
|
||||
</button>
|
||||
</form>
|
||||
<form method="post" action="{% url 'inventory:transformation-cancel' transformation.pk %}" class="d-inline ms-2">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm" onclick="return confirm('Отменить трансформацию?')">
|
||||
<i class="bi bi-x-lg me-1"></i>Отменить
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% elif transformation.status == 'completed' %}
|
||||
<div class="btn-group">
|
||||
<form method="post" action="{% url 'inventory:transformation-cancel' transformation.pk %}" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-outline-danger btn-sm" onclick="return confirm('Отменить проведенную трансформацию? Операции будут отменены.')">
|
||||
<i class="bi bi-x-lg me-1"></i>Отменить
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-3">
|
||||
<p class="text-muted small mb-1">Склад</p>
|
||||
<p class="fw-semibold">{{ transformation.warehouse.name }}</p>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<p class="text-muted small mb-1">Дата</p>
|
||||
<p class="fw-semibold">{{ transformation.date|date:"d.m.Y H:i" }}</p>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<p class="text-muted small mb-1">Создан</p>
|
||||
<p class="fw-semibold">{{ transformation.created_at|date:"d.m.Y H:i" }}</p>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<p class="text-muted small mb-1">Сотрудник</p>
|
||||
<p class="fw-semibold">{% if transformation.employee %}{{ transformation.employee.username }}{% else %}-{% endif %}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if transformation.comment %}
|
||||
<div class="mb-0">
|
||||
<p class="text-muted small mb-1">Комментарий</p>
|
||||
<p>{{ transformation.comment }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Добавление входного товара -->
|
||||
{% if transformation.status == 'draft' %}
|
||||
<div class="card border-0 shadow-sm mb-3">
|
||||
<div class="card-header bg-light py-3">
|
||||
<h6 class="mb-0"><i class="bi bi-box-arrow-in-down me-2 text-danger"></i>Добавить входной товар (что списываем)</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Компонент поиска товаров -->
|
||||
<div class="mb-3">
|
||||
{% include 'products/components/product_search_picker.html' with container_id='input-product-picker' title='Найти входной товар' warehouse_id=transformation.warehouse.id filter_in_stock_only=True categories=categories tags=tags add_button_text='Выбрать товар' content_height='250px' %}
|
||||
</div>
|
||||
|
||||
<!-- Форма добавления входного товара -->
|
||||
<form method="post" action="{% url 'inventory:transformation-add-input' transformation.pk %}" id="add-input-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="id_input_product" class="form-label">Товар <span class="text-danger">*</span></label>
|
||||
{{ input_form.product }}
|
||||
{% if input_form.product.errors %}
|
||||
<div class="text-danger small">{{ input_form.product.errors.0 }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="id_input_quantity" class="form-label">Количество <span class="text-danger">*</span></label>
|
||||
{{ input_form.quantity }}
|
||||
{% if input_form.quantity.errors %}
|
||||
<div class="text-danger small">{{ input_form.quantity.errors.0 }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-danger w-100">
|
||||
<i class="bi bi-check-circle me-1"></i>Добавить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Таблица входных товаров -->
|
||||
<div class="card border-0 shadow-sm mb-3">
|
||||
<div class="card-header bg-light py-3">
|
||||
<h6 class="mb-0"><i class="bi bi-box-arrow-in-down me-2 text-danger"></i>Входные товары (списание)</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-2">Товар</th>
|
||||
<th scope="col" class="px-3 py-2 text-end" style="width: 150px;">Количество</th>
|
||||
{% if transformation.status == 'draft' %}
|
||||
<th scope="col" class="px-3 py-2 text-end" style="width: 100px;"></th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for input in transformation.inputs.all %}
|
||||
<tr>
|
||||
<td class="px-3 py-2">
|
||||
<a href="{% url 'products:product-detail' input.product.id %}">{{ input.product.name }}</a>
|
||||
</td>
|
||||
<td class="px-3 py-2 text-end">{{ input.quantity|smart_quantity }}</td>
|
||||
{% if transformation.status == 'draft' %}
|
||||
<td class="px-3 py-2 text-end">
|
||||
<form method="post" action="{% url 'inventory:transformation-remove-input' transformation.pk input.pk %}" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger"
|
||||
onclick="return confirm('Удалить входной товар?');"
|
||||
title="Удалить">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="{% if transformation.status == 'draft' %}3{% else %}2{% endif %}" class="px-3 py-4 text-center text-muted">
|
||||
<i class="bi bi-inbox fs-1 d-block mb-2"></i>
|
||||
Входные товары не добавлены
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Добавление выходного товара -->
|
||||
{% if transformation.status == 'draft' %}
|
||||
<div class="card border-0 shadow-sm mb-3">
|
||||
<div class="card-header bg-light py-3">
|
||||
<h6 class="mb-0"><i class="bi bi-box-arrow-up me-2 text-success"></i>Добавить выходной товар (что получаем)</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Компонент поиска товаров -->
|
||||
<div class="mb-3">
|
||||
{% include 'products/components/product_search_picker.html' with container_id='output-product-picker' title='Найти выходной товар' categories=categories tags=tags add_button_text='Выбрать товар' content_height='250px' %}
|
||||
</div>
|
||||
|
||||
<!-- Форма добавления выходного товара -->
|
||||
<form method="post" action="{% url 'inventory:transformation-add-output' transformation.pk %}" id="add-output-form">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label for="id_output_product" class="form-label">Товар <span class="text-danger">*</span></label>
|
||||
{{ output_form.product }}
|
||||
{% if output_form.product.errors %}
|
||||
<div class="text-danger small">{{ output_form.product.errors.0 }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-3">
|
||||
<label for="id_output_quantity" class="form-label">Количество <span class="text-danger">*</span></label>
|
||||
{{ output_form.quantity }}
|
||||
{% if output_form.quantity.errors %}
|
||||
<div class="text-danger small">{{ output_form.quantity.errors.0 }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="col-md-3 d-flex align-items-end">
|
||||
<button type="submit" class="btn btn-success w-100">
|
||||
<i class="bi bi-check-circle me-1"></i>Добавить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Таблица выходных товаров -->
|
||||
<div class="card border-0 shadow-sm mb-3">
|
||||
<div class="card-header bg-light py-3">
|
||||
<h6 class="mb-0"><i class="bi bi-box-arrow-up me-2 text-success"></i>Выходные товары (оприходование)</h6>
|
||||
</div>
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-2">Товар</th>
|
||||
<th scope="col" class="px-3 py-2 text-end" style="width: 150px;">Количество</th>
|
||||
{% if transformation.status == 'draft' %}
|
||||
<th scope="col" class="px-3 py-2 text-end" style="width: 100px;"></th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for output in transformation.outputs.all %}
|
||||
<tr>
|
||||
<td class="px-3 py-2">
|
||||
<a href="{% url 'products:product-detail' output.product.id %}">{{ output.product.name }}</a>
|
||||
</td>
|
||||
<td class="px-3 py-2 text-end">{{ output.quantity|smart_quantity }}</td>
|
||||
{% if transformation.status == 'draft' %}
|
||||
<td class="px-3 py-2 text-end">
|
||||
<form method="post" action="{% url 'inventory:transformation-remove-output' transformation.pk output.pk %}" class="d-inline">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger"
|
||||
onclick="return confirm('Удалить выходной товар?');"
|
||||
title="Удалить">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="{% if transformation.status == 'draft' %}3{% else %}2{% endif %}" class="px-3 py-4 text-center text-muted">
|
||||
<i class="bi bi-inbox fs-1 d-block mb-2"></i>
|
||||
Выходные товары не добавлены
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- JS для компонента поиска -->
|
||||
<script src="{% static 'products/js/product-search-picker.js' %}"></script>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// Инициализация компонента поиска для входных товаров
|
||||
if (document.getElementById('input-product-picker')) {
|
||||
ProductSearchPicker.init('#input-product-picker', {
|
||||
onAddSelected: function(product, instance) {
|
||||
// Заполняем форму входного товара
|
||||
const productSelect = document.getElementById('id_input_product');
|
||||
if (productSelect) {
|
||||
// Добавляем опцию если её нет
|
||||
let option = productSelect.querySelector(`option[value="${product.id}"]`);
|
||||
if (!option) {
|
||||
option = document.createElement('option');
|
||||
option.value = product.id;
|
||||
option.text = product.text;
|
||||
productSelect.appendChild(option);
|
||||
}
|
||||
productSelect.value = product.id;
|
||||
}
|
||||
// Очищаем выбор в пикере
|
||||
instance.clearSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Инициализация компонента поиска для выходных товаров
|
||||
if (document.getElementById('output-product-picker')) {
|
||||
ProductSearchPicker.init('#output-product-picker', {
|
||||
onAddSelected: function(product, instance) {
|
||||
// Заполняем форму выходного товара
|
||||
const productSelect = document.getElementById('id_output_product');
|
||||
if (productSelect) {
|
||||
// Добавляем опцию если её нет
|
||||
let option = productSelect.querySelector(`option[value="${product.id}"]`);
|
||||
if (!option) {
|
||||
option = document.createElement('option');
|
||||
option.value = product.id;
|
||||
option.text = product.text;
|
||||
productSelect.appendChild(option);
|
||||
}
|
||||
productSelect.value = product.id;
|
||||
}
|
||||
// Очищаем выбор в пикере
|
||||
instance.clearSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
@@ -0,0 +1,54 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Создать трансформацию{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4 py-3">
|
||||
<!-- Breadcrumbs -->
|
||||
<nav aria-label="breadcrumb" class="mb-2">
|
||||
<ol class="breadcrumb breadcrumb-sm mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'inventory:transformation-list' %}">Трансформации</a></li>
|
||||
<li class="breadcrumb-item active">Создать</li>
|
||||
</ol>
|
||||
</nav>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-light py-3">
|
||||
<h5 class="mb-0">
|
||||
<i class="bi bi-arrow-repeat me-2"></i>Новая трансформация товара
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id_warehouse" class="form-label">Склад <span class="text-danger">*</span></label>
|
||||
{{ form.warehouse }}
|
||||
{% if form.warehouse.errors %}
|
||||
<div class="text-danger small">{{ form.warehouse.errors.0 }}</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="mb-3">
|
||||
<label for="id_comment" class="form-label">Комментарий</label>
|
||||
{{ form.comment }}
|
||||
</div>
|
||||
|
||||
<div class="d-flex gap-2">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<i class="bi bi-check-lg me-1"></i>Создать
|
||||
</button>
|
||||
<a href="{% url 'inventory:transformation-list' %}" class="btn btn-outline-secondary">
|
||||
Отмена
|
||||
</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
135
myproject/inventory/templates/inventory/transformation/list.html
Normal file
135
myproject/inventory/templates/inventory/transformation/list.html
Normal file
@@ -0,0 +1,135 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}Трансформации товаров{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid px-4 py-3">
|
||||
<!-- Header -->
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<h4 class="mb-0">
|
||||
<i class="bi bi-arrow-repeat me-2"></i>Трансформации товаров
|
||||
</h4>
|
||||
<a href="{% url 'inventory:transformation-create' %}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-lg me-1"></i>Создать трансформацию
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Messages -->
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
|
||||
{{ message }}
|
||||
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<!-- Table -->
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-0">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mb-0">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th scope="col" class="px-3 py-2">Номер</th>
|
||||
<th scope="col" class="px-3 py-2">Дата</th>
|
||||
<th scope="col" class="px-3 py-2">Склад</th>
|
||||
<th scope="col" class="px-3 py-2">Статус</th>
|
||||
<th scope="col" class="px-3 py-2">Входной товар</th>
|
||||
<th scope="col" class="px-3 py-2">Выходной товар</th>
|
||||
<th scope="col" class="px-3 py-2">Сотрудник</th>
|
||||
<th scope="col" class="px-3 py-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for transformation in transformations %}
|
||||
<tr>
|
||||
<td class="px-3 py-2">
|
||||
<a href="{% url 'inventory:transformation-detail' transformation.pk %}" class="fw-semibold text-decoration-none">
|
||||
{{ transformation.document_number }}
|
||||
</a>
|
||||
</td>
|
||||
<td class="px-3 py-2">{{ transformation.date|date:"d.m.Y H:i" }}</td>
|
||||
<td class="px-3 py-2">{{ transformation.warehouse.name }}</td>
|
||||
<td class="px-3 py-2">
|
||||
{% if transformation.status == 'draft' %}
|
||||
<span class="badge bg-warning text-dark">Черновик</span>
|
||||
{% elif transformation.status == 'completed' %}
|
||||
<span class="badge bg-success">Проведён</span>
|
||||
{% elif transformation.status == 'cancelled' %}
|
||||
<span class="badge bg-secondary">Отменён</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
{% for input in transformation.inputs.all %}
|
||||
<div class="small">{{ input.product.name }} - {{ input.quantity }} шт</div>
|
||||
{% empty %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
{% for output in transformation.outputs.all %}
|
||||
<div class="small">{{ output.product.name }} - {{ output.quantity }} шт</div>
|
||||
{% empty %}
|
||||
<span class="text-muted">-</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
<td class="px-3 py-2">
|
||||
{% if transformation.employee %}{{ transformation.employee.username }}{% else %}-{% endif %}
|
||||
</td>
|
||||
<td class="px-3 py-2 text-end">
|
||||
<a href="{% url 'inventory:transformation-detail' transformation.pk %}" class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-eye"></i>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="8" class="px-3 py-4 text-center text-muted">
|
||||
<i class="bi bi-inbox fs-1 d-block mb-2"></i>
|
||||
Трансформаций пока нет
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
<nav aria-label="Page navigation" class="mt-3">
|
||||
<ul class="pagination justify-content-center">
|
||||
{% if page_obj.has_previous %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page=1">Первая</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.previous_page_number }}">
|
||||
<i class="bi bi-chevron-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
|
||||
<li class="page-item active">
|
||||
<span class="page-link">
|
||||
Страница {{ page_obj.number }} из {{ page_obj.paginator.num_pages }}
|
||||
</span>
|
||||
</li>
|
||||
|
||||
{% if page_obj.has_next %}
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.next_page_number }}">
|
||||
<i class="bi bi-chevron-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li class="page-item">
|
||||
<a class="page-link" href="?page={{ page_obj.paginator.num_pages }}">Последняя</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user