427 lines
18 KiB
HTML
427 lines
18 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}{{ product.product_name }} - детальная информация{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="container">
|
||
<!-- Навигация -->
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<h2 class="page-title">{{ product.product_name }}</h2>
|
||
<div>
|
||
<a href="{{ url_for('edit_product', product_id=product.id) }}" class="btn btn-primary me-2">
|
||
<i class="fas fa-edit"></i> Редактировать
|
||
</a>
|
||
<a href="{{ url_for('catalog') }}" class="btn btn-secondary">
|
||
<i class="fas fa-arrow-left"></i> Назад в каталог
|
||
</a>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="row">
|
||
<!-- Левая колонка - основная информация -->
|
||
<div class="col-md-6">
|
||
<div class="card mb-4">
|
||
<div class="card-header" style="background: linear-gradient(135deg, var(--steel-blue) 0%, var(--light-steel-blue) 100%); color: white;">
|
||
<h5 class="mb-0"><i class="fas fa-info-circle"></i> Основная информация</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<table class="table table-borderless">
|
||
<tr>
|
||
<td style="width: 40%;"><strong>Артикул:</strong></td>
|
||
<td>{{ product.article }}</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Категория:</strong></td>
|
||
<td>{{ product.category_name }}</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Материал:</strong></td>
|
||
<td>
|
||
{% if product.material_name %}
|
||
{{ product.material_name }}
|
||
{% else %}
|
||
<span class="text-muted">Не указан</span>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
{% if product.description %}
|
||
<tr>
|
||
<td><strong>Описание:</strong></td>
|
||
<td>{{ product.description }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
<tr>
|
||
<td><strong>Базовая цена:</strong></td>
|
||
<td><span class="fw-bold fs-4" style="color: var(--steel-blue);">{{ product.min_price|int }} руб.</span></td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>В наличии:</strong></td>
|
||
<td>
|
||
{% if product.quantity_in_stock and product.quantity_in_stock > 0 %}
|
||
<span class="badge bg-success" style="font-size: 1rem;">{{ product.quantity_in_stock }} шт.</span>
|
||
{% else %}
|
||
<span class="badge bg-danger">Нет в наличии</span>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Информация о материале -->
|
||
{% if material %}
|
||
<div class="card mb-4">
|
||
<div class="card-header" style="background: linear-gradient(135deg, var(--light-steel-blue) 0%, #9fb6d4 100%);">
|
||
<h5 class="mb-0"><i class="fas fa-cube"></i> Информация о материале</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<table class="table table-borderless">
|
||
<tr>
|
||
<td style="width: 40%;"><strong>Материал:</strong></td>
|
||
<td>{{ material.material_name }}</td>
|
||
</tr>
|
||
{% if material.color %}
|
||
<tr>
|
||
<td><strong>Цвет:</strong></td>
|
||
<td>{{ material.color }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
{% if material.supplier %}
|
||
<tr>
|
||
<td><strong>Поставщик:</strong></td>
|
||
<td>{{ material.supplier }}</td>
|
||
</tr>
|
||
{% endif %}
|
||
<tr>
|
||
<td><strong>Цена за ед.:</strong></td>
|
||
<td>{{ material.price_per_unit|int }} руб./{{ material.unit }}</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>На складе:</strong></td>
|
||
<td>
|
||
{% if material.in_stock and material.in_stock > 0 %}
|
||
<span class="badge bg-success">{{ material.in_stock }} {{ material.unit }}</span>
|
||
{% else %}
|
||
<span class="badge bg-danger">Нет</span>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Правая колонка - производство -->
|
||
<div class="col-md-6">
|
||
<!-- Производственные характеристики -->
|
||
<div class="card mb-4">
|
||
<div class="card-header" style="background: linear-gradient(135deg, var(--light-steel-blue) 0%, #9fb6d4 100%);">
|
||
<h5 class="mb-0"><i class="fas fa-cogs"></i> Производственные характеристики</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<table class="table table-borderless">
|
||
<tr>
|
||
<td style="width: 50%;"><strong>Время изготовления:</strong></td>
|
||
<td>
|
||
{% if product.production_time_days %}
|
||
{{ product.production_time_days }} дн.
|
||
{% else %}
|
||
{{ material.production_time_days if material else 1 }} дн. (по умолчанию)
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Производительность:</strong></td>
|
||
<td>
|
||
{% if product.daily_capacity %}
|
||
{{ product.daily_capacity }} шт/день
|
||
{% else %}
|
||
{{ material.daily_capacity if material else 10 }} шт/день (по умолчанию)
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Себестоимость:</strong></td>
|
||
<td>
|
||
{% if product.cost_price %}
|
||
{{ product.cost_price|int }} руб.
|
||
{% elif material and material.price_per_unit %}
|
||
{{ (material.price_per_unit * 1.3)|int }} руб. (расчетная)
|
||
{% else %}
|
||
<span class="text-muted">Не указана</span>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
<tr>
|
||
<td><strong>Прибыль с единицы:</strong></td>
|
||
<td>
|
||
{% set cost = product.cost_price or (material.price_per_unit * 1.3 if material else 0) %}
|
||
{% if cost and cost > 0 %}
|
||
<span class="fw-bold text-success">{{ (product.min_price - cost)|int }} руб.</span>
|
||
<small class="text-muted">({{ ((product.min_price - cost) / product.min_price * 100)|round(1) }}%)</small>
|
||
{% else %}
|
||
<span class="text-muted">—</span>
|
||
{% endif %}
|
||
</td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Расчет стоимости с фиксированными процентами -->
|
||
<div class="card mb-4">
|
||
<div class="card-header" style="background: linear-gradient(135deg, var(--steel-blue) 0%, var(--light-steel-blue) 100%); color: white;">
|
||
<h5 class="mb-0"><i class="fas fa-truck"></i> Расчет стоимости со сборкой и доставкой</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row mb-3">
|
||
<div class="col-md-6">
|
||
<label for="quantity" class="form-label">Количество</label>
|
||
<input type="number" class="form-control" id="quantity" value="1" min="1" max="{{ product.quantity_in_stock or 100 }}"
|
||
onkeypress="return isNumberKey(event)"
|
||
oninput="this.value = this.value.replace(/[^0-9]/g, '')">
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Результаты расчета -->
|
||
<div class="table-responsive mt-3">
|
||
<table class="table table-bordered">
|
||
<tr>
|
||
<th style="width: 50%;">Базовая стоимость:</th>
|
||
<td class="text-end fw-bold" id="baseCost">0 руб.</td>
|
||
</tr>
|
||
<tr>
|
||
<th>Стоимость сборки (15%):</th>
|
||
<td class="text-end" id="assemblyCost">0 руб.</td>
|
||
</tr>
|
||
<tr>
|
||
<th>Стоимость доставки (10%):</th>
|
||
<td class="text-end" id="deliveryCost">0 руб.</td>
|
||
</tr>
|
||
<tr class="table-info">
|
||
<th><span class="fs-5">ИТОГОВАЯ СТОИМОСТЬ:</span></th>
|
||
<td class="text-end"><span class="fw-bold fs-4" style="color: var(--steel-blue);" id="totalCost">0 руб.</span></td>
|
||
</tr>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Статистика продаж -->
|
||
<div class="card mb-4">
|
||
<div class="card-header" style="background: linear-gradient(135deg, var(--light-steel-blue) 0%, #9fb6d4 100%);">
|
||
<h5 class="mb-0"><i class="fas fa-chart-bar"></i> Статистика продаж</h5>
|
||
</div>
|
||
<div class="card-body">
|
||
<div class="row text-center">
|
||
<div class="col-4">
|
||
<div class="p-3">
|
||
<div class="h3 mb-1 fw-bold" style="color: var(--steel-blue);">{{ orders_stats.total_orders or 0 }}</div>
|
||
<small class="text-muted">Всего заказов</small>
|
||
</div>
|
||
</div>
|
||
<div class="col-4">
|
||
<div class="p-3">
|
||
<div class="h3 mb-1 fw-bold" style="color: var(--steel-blue);">{{ orders_stats.total_sold or 0 }}</div>
|
||
<small class="text-muted">Продано единиц</small>
|
||
</div>
|
||
</div>
|
||
<div class="col-4">
|
||
<div class="p-3">
|
||
<div class="h3 mb-1 fw-bold" style="color: var(--steel-blue);">{{ (orders_stats.avg_order_size or 0)|int }}</div>
|
||
<small class="text-muted">Средний заказ</small>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- JavaScript для калькулятора -->
|
||
<script>
|
||
// Функция для проверки ввода только цифр
|
||
function isNumberKey(evt) {
|
||
var charCode = (evt.which) ? evt.which : evt.keyCode;
|
||
// Разрешаем цифры, backspace, delete, tab, enter, стрелки
|
||
if (charCode == 8 || charCode == 46 || charCode == 9 || charCode == 13 ||
|
||
charCode == 37 || charCode == 38 || charCode == 39 || charCode == 40) {
|
||
return true;
|
||
}
|
||
// Разрешаем только цифры (0-9)
|
||
if (charCode >= 48 && charCode <= 57) {
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
// Получаем данные товара
|
||
const price = {{ product.min_price }};
|
||
const assemblyPercent = 15;
|
||
const deliveryPercent = 10;
|
||
|
||
// Элементы для расчета стоимости
|
||
const quantityInput = document.getElementById('quantity');
|
||
const baseCostSpan = document.getElementById('baseCost');
|
||
const assemblyCostSpan = document.getElementById('assemblyCost');
|
||
const deliveryCostSpan = document.getElementById('deliveryCost');
|
||
const totalCostSpan = document.getElementById('totalCost');
|
||
|
||
// Функция расчета стоимости
|
||
function calculateTotalCost() {
|
||
const quantity = parseInt(quantityInput.value) || 1;
|
||
|
||
// Базовая стоимость
|
||
const baseCost = price * quantity;
|
||
|
||
// Стоимость сборки и доставки (фиксированные проценты)
|
||
const assemblyCost = baseCost * assemblyPercent / 100;
|
||
const deliveryCost = baseCost * deliveryPercent / 100;
|
||
|
||
// Итоговая стоимость
|
||
const totalCost = baseCost + assemblyCost + deliveryCost;
|
||
|
||
// Обновляем значения
|
||
baseCostSpan.textContent = baseCost.toLocaleString('ru-RU') + ' руб.';
|
||
assemblyCostSpan.textContent = assemblyCost.toLocaleString('ru-RU') + ' руб.';
|
||
deliveryCostSpan.textContent = deliveryCost.toLocaleString('ru-RU') + ' руб.';
|
||
totalCostSpan.textContent = totalCost.toLocaleString('ru-RU') + ' руб.';
|
||
}
|
||
|
||
// Слушатель для изменения количества
|
||
quantityInput.addEventListener('input', calculateTotalCost);
|
||
|
||
// Запрещаем вставку текста
|
||
quantityInput.addEventListener('paste', function(e) {
|
||
e.preventDefault();
|
||
return false;
|
||
});
|
||
|
||
// Первоначальный расчет
|
||
calculateTotalCost();
|
||
});
|
||
</script>
|
||
|
||
<style>
|
||
.page-title {
|
||
color: var(--steel-blue);
|
||
font-size: 2rem;
|
||
font-weight: 600;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.card {
|
||
border: 1px solid var(--light-steel-blue);
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 8px rgba(70, 130, 180, 0.1);
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.card:hover {
|
||
box-shadow: 0 8px 16px rgba(70, 130, 180, 0.15);
|
||
}
|
||
|
||
.table-borderless td {
|
||
padding: 8px 0;
|
||
border: none;
|
||
}
|
||
|
||
.table-bordered {
|
||
border-color: var(--light-steel-blue);
|
||
}
|
||
|
||
.table-bordered th,
|
||
.table-bordered td {
|
||
border-color: var(--light-steel-blue);
|
||
padding: 12px;
|
||
}
|
||
|
||
.table-info {
|
||
background-color: rgba(70, 130, 180, 0.1);
|
||
}
|
||
|
||
.form-label {
|
||
color: var(--steel-blue);
|
||
font-weight: 500;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.form-control {
|
||
border: 2px solid var(--light-steel-blue);
|
||
border-radius: 8px;
|
||
padding: 10px 15px;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.form-control:focus {
|
||
border-color: var(--steel-blue);
|
||
box-shadow: 0 0 0 0.2rem rgba(70, 130, 180, 0.25);
|
||
outline: none;
|
||
}
|
||
|
||
.btn {
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 10px 20px;
|
||
transition: all 0.2s ease;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, var(--steel-blue) 0%, #3a6d99 100%);
|
||
color: white;
|
||
}
|
||
|
||
.btn-primary:hover {
|
||
background: linear-gradient(135deg, #3a6d99 0%, var(--steel-blue) 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 10px rgba(70, 130, 180, 0.3);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: linear-gradient(135deg, var(--light-steel-blue) 0%, #9fb6d4 100%);
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.btn-secondary:hover {
|
||
background: linear-gradient(135deg, #9fb6d4 0%, var(--light-steel-blue) 100%);
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 10px rgba(176, 196, 222, 0.3);
|
||
}
|
||
|
||
.badge {
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
padding: 6px 12px;
|
||
}
|
||
|
||
.bg-light {
|
||
background-color: #f8f9fa !important;
|
||
}
|
||
|
||
.text-primary {
|
||
color: var(--steel-blue) !important;
|
||
}
|
||
|
||
.text-success {
|
||
color: #28a745 !important;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.page-title {
|
||
font-size: 1.5rem;
|
||
}
|
||
|
||
.d-flex.justify-content-between {
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.d-flex.justify-content-between .btn {
|
||
width: 100%;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %} |