Добавлена отладочная страница для суперюзеров (Inventory Debug)

Реализация:
- Создан view debug_inventory_page (только для суперюзеров)
- URL: /inventory/debug/
- Компактный дизайн с минимальными отступами и маленьким шрифтом (10-11px)

Функционал:
1. Показывает полную картину инвентаризации на одной странице:
   - Заказы (Order) - номер, статус, покупатель, is_returned
   - Остатки (Stock) - доступно, зарезервировано, свободно
   - Партии (StockBatch) - количество, активность, дата поступления
   - Резервы (Reservation) - статус (reserved/converted_to_sale/released), заказ, даты
   - Продажи (Sale) - количество, цены, заказ
   - Списания (SaleBatchAllocation) - откуда списано, сколько

2. Фильтры:
   - По товару (dropdown с названием и SKU)
   - По номеру заказа (текстовое поле)
   - По складу (dropdown)
   - Кнопка 'Применить' и 'Сбросить'

3. UI:
   - Цветовая индикация статусов резервов
   - Бейджи для ключевых данных
   - Компактные таблицы Bootstrap
   - Неактивные партии выделены красным
   - Ограничение в 100 записей на таблицу для производительности

4. Навигация:
   - Ссылка 🔧 Debug в navbar (видна только суперюзерам)
   - Красный цвет для видимости

Юзкейс:
Суперюзер принимает товар на склад → оформляет заказ → меняет статусы →
переходит на /inventory/debug/ → видит полную картину всех изменений

Файлы:
- inventory/views/debug_views.py - новый view
- inventory/templates/inventory/debug_page.html - шаблон
- inventory/urls.py - добавлен роут
- templates/navbar.html - добавлена ссылка для суперюзеров
This commit is contained in:
2025-12-01 09:57:06 +03:00
parent 7b1922c186
commit 6bb15db5a0
4 changed files with 534 additions and 1 deletions

View File

@@ -0,0 +1,417 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Отладка Inventory - Суперюзер{% endblock %}
{% block extra_css %}
<style>
/* Компактный дизайн с маленькими отступами и шрифтом */
.debug-page {
font-size: 10px;
line-height: 1.3;
}
.debug-page h2 {
font-size: 14px;
margin-top: 15px;
margin-bottom: 8px;
font-weight: bold;
color: #333;
border-bottom: 2px solid #007bff;
padding-bottom: 3px;
}
.debug-page h3 {
font-size: 12px;
margin-top: 10px;
margin-bottom: 5px;
font-weight: bold;
color: #555;
}
.debug-page .table {
font-size: 10px;
margin-bottom: 8px;
}
.debug-page .table th {
padding: 3px 5px;
background-color: #f8f9fa;
font-weight: 600;
border: 1px solid #dee2e6;
}
.debug-page .table td {
padding: 2px 5px;
border: 1px solid #dee2e6;
vertical-align: middle;
}
.debug-page .badge {
font-size: 9px;
padding: 2px 5px;
}
.debug-page .filter-form {
background: #f8f9fa;
padding: 10px;
border-radius: 5px;
margin-bottom: 15px;
}
.debug-page .filter-form .form-control,
.debug-page .filter-form .form-select {
font-size: 11px;
padding: 3px 8px;
height: auto;
}
.debug-page .filter-form label {
font-size: 11px;
margin-bottom: 2px;
font-weight: 600;
}
.debug-page .btn-sm {
font-size: 11px;
padding: 3px 10px;
}
.status-reserved { background-color: #fff3cd; }
.status-converted { background-color: #d1ecf1; }
.status-released { background-color: #d4edda; }
.inactive-row { background-color: #f8d7da; opacity: 0.7; }
.section-card {
border: 1px solid #dee2e6;
border-radius: 5px;
padding: 8px;
margin-bottom: 10px;
background: white;
}
.summary-box {
display: inline-block;
padding: 3px 8px;
margin-right: 10px;
background: #e9ecef;
border-radius: 3px;
font-size: 10px;
}
.text-muted-small {
color: #6c757d;
font-size: 9px;
}
</style>
{% endblock %}
{% block content %}
<div class="container-fluid debug-page mt-3">
<div class="row">
<div class="col-12">
<h2>🔧 Отладка Inventory (только для суперюзеров)</h2>
<!-- Фильтры -->
<div class="filter-form">
<form method="get" class="row g-2">
<div class="col-md-4">
<label for="product">Товар:</label>
<select name="product" id="product" class="form-select form-select-sm">
<option value="">-- Все товары --</option>
{% for prod in products %}
<option value="{{ prod.id }}" {% if product_id == prod.id|stringformat:"s" %}selected{% endif %}>
{{ prod.name }} ({{ prod.sku }})
</option>
{% endfor %}
</select>
</div>
<div class="col-md-3">
<label for="order">Номер заказа:</label>
<input type="text" name="order" id="order" class="form-control form-control-sm"
value="{{ order_number|default:'' }}" placeholder="ORD-100">
</div>
<div class="col-md-3">
<label for="warehouse">Склад:</label>
<select name="warehouse" id="warehouse" class="form-select form-select-sm">
<option value="">-- Все склады --</option>
{% for wh in warehouses %}
<option value="{{ wh.id }}" {% if warehouse_id == wh.id|stringformat:"s" %}selected{% endif %}>
{{ wh.name }}
</option>
{% endfor %}
</select>
</div>
<div class="col-md-2 d-flex align-items-end">
<button type="submit" class="btn btn-primary btn-sm me-2">Применить</button>
<a href="{% url 'inventory:debug_page' %}" class="btn btn-secondary btn-sm">Сбросить</a>
</div>
</form>
</div>
{% if selected_product or selected_order or selected_warehouse %}
<div class="alert alert-info py-2" style="font-size: 11px;">
<strong>Активные фильтры:</strong>
{% if selected_product %}Товар: <strong>{{ selected_product.name }}</strong>{% endif %}
{% if selected_order %}Заказ: <strong>{{ selected_order.order_number }}</strong>{% endif %}
{% if selected_warehouse %}Склад: <strong>{{ selected_warehouse.name }}</strong>{% endif %}
</div>
{% endif %}
<!-- ЗАКАЗЫ -->
<div class="section-card">
<h3>📦 Заказы ({{ orders.count }})</h3>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover">
<thead>
<tr>
<th>ID</th>
<th>Номер</th>
<th>Статус</th>
<th>Возврат</th>
<th>Покупатель</th>
<th>Товары</th>
<th>Сумма</th>
<th>Создан</th>
</tr>
</thead>
<tbody>
{% for order in orders %}
<tr>
<td>{{ order.id }}</td>
<td><strong>{{ order.order_number }}</strong></td>
<td>
<span class="badge bg-secondary">{{ order.status.name|default:"?" }}</span>
</td>
<td>
{% if order.is_returned %}
<span class="badge bg-warning">Возврат</span>
{% else %}
<span class="text-muted">-</span>
{% endif %}
</td>
<td>{{ order.customer.name|default:"-" }}</td>
<td>{{ order.items.count }} шт</td>
<td>{{ order.total_price|floatformat:2 }}</td>
<td class="text-muted-small">{{ order.created_at|date:"d.m.Y H:i" }}</td>
</tr>
{% empty %}
<tr><td colspan="8" class="text-center text-muted">Нет заказов</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- ОСТАТКИ (Stock) -->
<div class="section-card">
<h3>📊 Остатки Stock ({{ stocks.count }})</h3>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover">
<thead>
<tr>
<th>ID</th>
<th>Товар</th>
<th>Склад</th>
<th>Доступно</th>
<th>Зарезервировано</th>
<th>Свободно</th>
<th>Обновлено</th>
</tr>
</thead>
<tbody>
{% for stock in stocks %}
<tr>
<td>{{ stock.id }}</td>
<td><strong>{{ stock.product.name }}</strong></td>
<td>{{ stock.warehouse.name }}</td>
<td><span class="badge bg-primary">{{ stock.quantity_available }}</span></td>
<td><span class="badge bg-warning text-dark">{{ stock.quantity_reserved }}</span></td>
<td><span class="badge bg-success">{{ stock.quantity_free }}</span></td>
<td class="text-muted-small">{{ stock.updated_at|date:"d.m.Y H:i:s" }}</td>
</tr>
{% empty %}
<tr><td colspan="7" class="text-center text-muted">Нет данных Stock</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- ПАРТИИ (StockBatch) -->
<div class="section-card">
<h3>📦 Партии StockBatch ({{ stock_batches.count }})</h3>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover">
<thead>
<tr>
<th>ID</th>
<th>Товар</th>
<th>Склад</th>
<th>Кол-во</th>
<th>Себест.</th>
<th>Активна</th>
<th>Создана</th>
</tr>
</thead>
<tbody>
{% for batch in stock_batches %}
<tr {% if not batch.is_active %}class="inactive-row"{% endif %}>
<td>{{ batch.id }}</td>
<td><strong>{{ batch.product.name }}</strong></td>
<td>{{ batch.warehouse.name }}</td>
<td><span class="badge bg-info text-dark">{{ batch.quantity }}</span></td>
<td>{{ batch.cost_per_unit|floatformat:2 }}</td>
<td>
{% if batch.is_active %}
<span class="badge bg-success">Да</span>
{% else %}
<span class="badge bg-danger">Нет</span>
{% endif %}
</td>
<td class="text-muted-small">{{ batch.created_at|date:"d.m.Y H:i" }}</td>
</tr>
{% empty %}
<tr><td colspan="7" class="text-center text-muted">Нет партий</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- РЕЗЕРВЫ (Reservation) -->
<div class="section-card">
<h3>🔒 Резервы Reservation ({{ reservations.count }})</h3>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover">
<thead>
<tr>
<th>ID</th>
<th>Товар</th>
<th>Склад</th>
<th>Кол-во</th>
<th>Статус</th>
<th>Заказ</th>
<th>Создан</th>
<th>Преобразован</th>
<th>Освобожден</th>
</tr>
</thead>
<tbody>
{% for res in reservations %}
<tr class="
{% if res.status == 'reserved' %}status-reserved
{% elif res.status == 'converted_to_sale' %}status-converted
{% elif res.status == 'released' %}status-released
{% endif %}
">
<td>{{ res.id }}</td>
<td><strong>{{ res.product.name }}</strong></td>
<td>{{ res.warehouse.name }}</td>
<td><span class="badge bg-dark">{{ res.quantity }}</span></td>
<td>
{% if res.status == 'reserved' %}
<span class="badge bg-warning text-dark">Зарезервирован</span>
{% elif res.status == 'converted_to_sale' %}
<span class="badge bg-info">В продажу</span>
{% elif res.status == 'released' %}
<span class="badge bg-success">Освобожден</span>
{% else %}
<span class="badge bg-secondary">{{ res.status }}</span>
{% endif %}
</td>
<td>
{% if res.order_item.order %}
<strong>{{ res.order_item.order.order_number }}</strong>
{% else %}
-
{% endif %}
</td>
<td class="text-muted-small">{{ res.created_at|date:"d.m.Y H:i:s" }}</td>
<td class="text-muted-small">
{% if res.converted_at %}{{ res.converted_at|date:"d.m.Y H:i:s" }}{% else %}-{% endif %}
</td>
<td class="text-muted-small">
{% if res.released_at %}{{ res.released_at|date:"d.m.Y H:i:s" }}{% else %}-{% endif %}
</td>
</tr>
{% empty %}
<tr><td colspan="9" class="text-center text-muted">Нет резервов</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- ПРОДАЖИ (Sale) -->
<div class="section-card">
<h3>💰 Продажи Sale ({{ sales.count }})</h3>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover">
<thead>
<tr>
<th>ID</th>
<th>Товар</th>
<th>Склад</th>
<th>Кол-во</th>
<th>Цена продажи</th>
<th>Себестоимость</th>
<th>Заказ</th>
<th>Документ</th>
<th>Создана</th>
</tr>
</thead>
<tbody>
{% for sale in sales %}
<tr>
<td>{{ sale.id }}</td>
<td><strong>{{ sale.product.name }}</strong></td>
<td>{{ sale.warehouse.name }}</td>
<td><span class="badge bg-primary">{{ sale.quantity }}</span></td>
<td>{{ sale.sale_price|floatformat:2 }}</td>
<td>{{ sale.cost_price|floatformat:2 }}</td>
<td>
{% if sale.order %}
<strong>{{ sale.order.order_number }}</strong>
{% else %}
-
{% endif %}
</td>
<td class="text-muted-small">{{ sale.document_number|default:"-" }}</td>
<td class="text-muted-small">{{ sale.created_at|date:"d.m.Y H:i:s" }}</td>
</tr>
{% empty %}
<tr><td colspan="9" class="text-center text-muted">Нет продаж</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- СПИСАНИЯ (SaleBatchAllocation) -->
<div class="section-card">
<h3>📤 Списания SaleBatchAllocation ({{ allocations.count }})</h3>
<div class="table-responsive">
<table class="table table-sm table-bordered table-hover">
<thead>
<tr>
<th>ID</th>
<th>Sale ID</th>
<th>Товар</th>
<th>Партия ID</th>
<th>Кол-во списано</th>
<th>Себест. за ед.</th>
</tr>
</thead>
<tbody>
{% for alloc in allocations %}
<tr>
<td>{{ alloc.id }}</td>
<td>{{ alloc.sale.id }}</td>
<td><strong>{{ alloc.sale.product.name }}</strong></td>
<td>{{ alloc.batch.id }}</td>
<td><span class="badge bg-danger">{{ alloc.quantity }}</span></td>
<td>{{ alloc.cost_per_unit|floatformat:2 }}</td>
</tr>
{% empty %}
<tr><td colspan="6" class="text-center text-muted">Нет списаний</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="alert alert-secondary py-2 mt-3" style="font-size: 10px;">
<strong>Примечание:</strong> Показаны последние 100 записей для каждой таблицы.
Используйте фильтры для уточнения результатов.
</div>
</div>
</div>
</div>
{% endblock %}