465 lines
18 KiB
HTML
465 lines
18 KiB
HTML
{% extends "base.html" %}
|
||
|
||
{% block title %}История заказов - {{ client.name }}{% endblock %}
|
||
|
||
{% block content %}
|
||
<div class="sales-history-container">
|
||
<!-- Заголовок с кнопкой назад -->
|
||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||
<div>
|
||
<a href="javascript:history.back()" class="btn btn-back me-3">
|
||
<i class="fas fa-arrow-left"></i> Назад
|
||
</a>
|
||
<h2 class="page-title">История заказов - {{ client.name }}</h2>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Информация о клиенте -->
|
||
<div class="partner-info-card mb-4">
|
||
<div class="partner-header">
|
||
<div class="partner-name-line">
|
||
<span class="partner-type">
|
||
{% if client.contractor_type_name %}
|
||
{{ client.contractor_type_name }}
|
||
{% else %}
|
||
Тип не указан
|
||
{% endif %}
|
||
</span>
|
||
<span class="divider">|</span>
|
||
<span class="partner-name">{{ client.name }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="partner-details">
|
||
<div class="row">
|
||
<div class="col-md-6">
|
||
<div class="info-item">
|
||
<span class="info-label">Директор:</span>
|
||
<span class="info-value">{{ client.director or 'Не указан' }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">Телефон:</span>
|
||
<span class="info-value">{{ client.phone or 'Не указан' }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<div class="info-item">
|
||
<span class="info-label">Email:</span>
|
||
<span class="info-value">{{ client.email or 'Не указан' }}</span>
|
||
</div>
|
||
<div class="info-item">
|
||
<span class="info-label">ИНН:</span>
|
||
<span class="info-value">{{ client.inn or 'Не указан' }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Общая статистика -->
|
||
<div class="stats-card mb-4">
|
||
<h4 class="stats-title">Общая статистика</h4>
|
||
<div class="row">
|
||
<div class="col-md-3">
|
||
<div class="stat-item">
|
||
<div class="stat-number">{{ total_stats.total_orders or 0 }}</div>
|
||
<div class="stat-label">Всего заказов</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="stat-item">
|
||
<div class="stat-number">{{ total_stats.total_quantity or 0 }}</div>
|
||
<div class="stat-label">Общее количество</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="stat-item">
|
||
<div class="stat-number">{{ "{:,.2f}".format(total_stats.total_amount or 0) }} руб.</div>
|
||
<div class="stat-label">Общая сумма</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-md-3">
|
||
<div class="stat-item">
|
||
<div class="stat-number">{{ total_stats.completed_orders or 0 }}</div>
|
||
<div class="stat-label">Выполненных</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Таблица истории заказов -->
|
||
<div class="sales-table-card">
|
||
<h4 class="table-title">Детализация заказов</h4>
|
||
|
||
{% if orders %}
|
||
<div class="accordion" id="ordersAccordion">
|
||
{% for order in orders %}
|
||
<div class="accordion-item mb-3 border rounded">
|
||
<h2 class="accordion-header" id="heading{{ order.id }}">
|
||
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#collapse{{ order.id }}" aria-expanded="false" aria-controls="collapse{{ order.id }}">
|
||
<div class="row w-100">
|
||
<div class="col-md-3">
|
||
<strong>Заказ #{{ order.id }}</strong>
|
||
</div>
|
||
<div class="col-md-3">
|
||
{{ order.order_date[:10] }}
|
||
</div>
|
||
<div class="col-md-3">
|
||
{{ order['items']|length }} позиций
|
||
</div>
|
||
<div class="col-md-3 text-end">
|
||
<span class="badge {% if order.status == 'Выполнен' %}bg-success{% elif order.status == 'Отменен' %}bg-danger{% elif order.status == 'В обработке' %}bg-warning{% else %}bg-secondary{% endif %} me-2">
|
||
{{ order.status }}
|
||
</span>
|
||
<strong>{{ "{:,.2f}".format(order.total_amount) }} руб.</strong>
|
||
</div>
|
||
</div>
|
||
</button>
|
||
</h2>
|
||
<div id="collapse{{ order.id }}" class="accordion-collapse collapse" aria-labelledby="heading{{ order.id }}" data-bs-parent="#ordersAccordion">
|
||
<div class="accordion-body">
|
||
<!-- Детальная информация о заказе -->
|
||
<div class="row mb-3">
|
||
<div class="col-md-6">
|
||
<p><strong>Дата заказа:</strong> {{ order.order_date[:10] }}</p>
|
||
<p><strong>Срок изготовления:</strong> {{ order.production_days or 1 }} дн.</p>
|
||
</div>
|
||
<div class="col-md-6">
|
||
<p><strong>Дата готовности:</strong> {{ order.production_end_date[:10] if order.production_end_date else '-' }}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Таблица с товарами -->
|
||
<h5>Состав заказа:</h5>
|
||
<div class="table-responsive">
|
||
<table class="table table-sm table-bordered">
|
||
<thead class="table-light">
|
||
<tr>
|
||
<th>Товар</th>
|
||
<th>Артикул</th>
|
||
<th>Количество</th>
|
||
<th>Цена за ед.</th>
|
||
<th>Сумма</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for item in order['items'] %}
|
||
{% set item_sum = item['price_at_moment'] * item['quantity'] %}
|
||
<tr>
|
||
<td>{{ item['product_name'] }}</td>
|
||
<td>{{ item['article'] }}</td>
|
||
<td>{{ "{:,}".format(item['quantity']) }} ед.</td>
|
||
<td>{{ "{:,.2f}".format(item['price_at_moment']) }} руб.</td>
|
||
<td>{{ "{:,.2f}".format(item_sum) }} руб.</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
|
||
<!-- Расчет стоимости с учетом сборки и доставки -->
|
||
<div class="row mt-3">
|
||
<div class="col-md-8 offset-md-4">
|
||
<div class="calculation-details p-3" style="background-color: #f8f9fa; border-radius: 8px;">
|
||
<div class="d-flex justify-content-between mb-2">
|
||
<span>Базовая сумма товаров:</span>
|
||
<strong>{{ "{:,.2f}".format(order.base_amount) }} руб.</strong>
|
||
</div>
|
||
<div class="d-flex justify-content-between mb-2">
|
||
<span>Сборка ({{ order.assembly_percent }}%):</span>
|
||
<span class="text-primary">+{{ "{:,.2f}".format(order.assembly_cost) }} руб.</span>
|
||
</div>
|
||
<div class="d-flex justify-content-between mb-2">
|
||
<span>Доставка ({{ order.delivery_percent }}%):</span>
|
||
<span class="text-primary">+{{ "{:,.2f}".format(order.delivery_cost) }} руб.</span>
|
||
</div>
|
||
<div class="d-flex justify-content-between pt-2 border-top">
|
||
<span class="fw-bold">ИТОГОВАЯ СУММА ЗАКАЗА:</span>
|
||
<span class="fw-bold fs-5" style="color: var(--steel-blue);">{{ "{:,.2f}".format(order.total_amount) }} руб.</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Форма изменения статуса заказа -->
|
||
<div class="row mt-3">
|
||
<div class="col-md-6">
|
||
<form method="POST" action="{{ url_for('update_order_status', order_id=order.id) }}" class="d-flex align-items-center gap-2">
|
||
<label class="mb-0"><strong>Статус заказа:</strong></label>
|
||
<select name="status" class="form-select form-select-sm" style="width: 150px;" onchange="this.form.submit()">
|
||
<option value="Новый" {% if order.status == 'Новый' %}selected{% endif %}>Новый</option>
|
||
<option value="В обработке" {% if order.status == 'В обработке' %}selected{% endif %}>В обработке</option>
|
||
<option value="Выполнен" {% if order.status == 'Выполнен' %}selected{% endif %}>Выполнен</option>
|
||
<option value="Отменен" {% if order.status == 'Отменен' %}selected{% endif %}>Отменен</option>
|
||
</select>
|
||
</form>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% else %}
|
||
<div class="alert alert-info">
|
||
<i class="fas fa-info-circle"></i> Нет данных о заказах
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.btn-back {
|
||
background: linear-gradient(135deg, var(--light-steel-blue) 0%, #9fb6d4 100%);
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 10px 20px;
|
||
color: #2c3e50;
|
||
transition: all 0.3s ease;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.btn-back: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);
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.btn-primary {
|
||
background: linear-gradient(135deg, var(--steel-blue) 0%, #3a6d99 100%);
|
||
border: none;
|
||
border-radius: 8px;
|
||
padding: 10px 20px;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
color: white;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.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);
|
||
}
|
||
|
||
.page-title {
|
||
color: var(--steel-blue);
|
||
font-size: 1.8rem;
|
||
font-weight: 600;
|
||
display: inline-block;
|
||
margin: 0;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.partner-info-card {
|
||
background: linear-gradient(135deg, var(--alice-blue) 0%, #E6F2FF 100%);
|
||
border: 1px solid var(--light-steel-blue);
|
||
border-radius: 15px;
|
||
padding: 20px;
|
||
box-shadow: 0 4px 10px rgba(70, 130, 180, 0.1);
|
||
}
|
||
|
||
.partner-header {
|
||
margin-bottom: 20px;
|
||
padding-bottom: 15px;
|
||
border-bottom: 2px solid var(--light-steel-blue);
|
||
}
|
||
|
||
.partner-name-line {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.partner-type {
|
||
background-color: transparent;
|
||
color: #2c3e50 !important;
|
||
padding: 5px 15px;
|
||
border-radius: 20px;
|
||
font-size: 1rem;
|
||
font-weight: 600;
|
||
text-transform: uppercase;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
border: 2px solid var(--light-steel-blue);
|
||
}
|
||
|
||
.divider {
|
||
color: var(--steel-blue);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.partner-name {
|
||
color: #2c3e50;
|
||
font-size: 1.3rem;
|
||
font-weight: 600;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.info-item {
|
||
margin-bottom: 10px;
|
||
padding: 8px 15px;
|
||
background-color: rgba(255, 255, 255, 0.5);
|
||
border-radius: 8px;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.info-label {
|
||
color: var(--steel-blue);
|
||
font-weight: 600;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.info-value {
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.stats-card {
|
||
background: linear-gradient(135deg, var(--alice-blue) 0%, #E6F2FF 100%);
|
||
border: 1px solid var(--light-steel-blue);
|
||
border-radius: 15px;
|
||
padding: 20px;
|
||
box-shadow: 0 4px 10px rgba(70, 130, 180, 0.1);
|
||
}
|
||
|
||
.stats-title, .table-title {
|
||
color: var(--steel-blue);
|
||
font-size: 1.3rem;
|
||
font-weight: 600;
|
||
margin-bottom: 20px;
|
||
border-bottom: 2px solid var(--light-steel-blue);
|
||
padding-bottom: 10px;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.stat-item {
|
||
text-align: center;
|
||
padding: 15px;
|
||
background: linear-gradient(135deg, white 0%, #f8f9fa 100%);
|
||
border: 2px solid var(--light-steel-blue);
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 8px rgba(70, 130, 180, 0.15);
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.stat-item:hover {
|
||
transform: translateY(-3px);
|
||
box-shadow: 0 8px 15px rgba(70, 130, 180, 0.25);
|
||
border-color: var(--steel-blue);
|
||
}
|
||
|
||
.stat-number {
|
||
color: #2c3e50 !important;
|
||
font-size: 2rem;
|
||
font-weight: 700;
|
||
line-height: 1.2;
|
||
margin-bottom: 5px;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.stat-label {
|
||
color: #6c757d;
|
||
font-size: 0.9rem;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.5px;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
}
|
||
|
||
.sales-table-card {
|
||
background: linear-gradient(135deg, var(--alice-blue) 0%, #E6F2FF 100%);
|
||
border: 1px solid var(--light-steel-blue);
|
||
border-radius: 15px;
|
||
padding: 20px;
|
||
box-shadow: 0 4px 10px rgba(70, 130, 180, 0.1);
|
||
}
|
||
|
||
.accordion-item {
|
||
border: 1px solid var(--light-steel-blue);
|
||
border-radius: 10px !important;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.accordion-button {
|
||
background: linear-gradient(135deg, var(--alice-blue) 0%, #E6F2FF 100%);
|
||
color: #2c3e50;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
padding: 15px 20px;
|
||
}
|
||
|
||
.accordion-button:not(.collapsed) {
|
||
background: linear-gradient(135deg, var(--light-steel-blue) 0%, #9fb6d4 100%);
|
||
color: #2c3e50;
|
||
}
|
||
|
||
.accordion-button:focus {
|
||
box-shadow: none;
|
||
border-color: var(--steel-blue);
|
||
}
|
||
|
||
.accordion-body {
|
||
background-color: white;
|
||
padding: 20px;
|
||
}
|
||
|
||
.badge {
|
||
padding: 6px 12px;
|
||
font-size: 0.85rem;
|
||
border-radius: 20px;
|
||
font-family: 'Franklin Gothic', 'Arial Narrow', Arial, sans-serif;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.badge.bg-success {
|
||
background: linear-gradient(135deg, #28a745 0%, #218838 100%) !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.badge.bg-warning {
|
||
background: linear-gradient(135deg, #ffc107 0%, #e0a800 100%) !important;
|
||
color: #2c3e50 !important;
|
||
}
|
||
|
||
.badge.bg-danger {
|
||
background: linear-gradient(135deg, #dc3545 0%, #c82333 100%) !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.badge.bg-secondary {
|
||
background: linear-gradient(135deg, #6c757d 0%, #5a6268 100%) !important;
|
||
color: white !important;
|
||
}
|
||
|
||
.table-sm td, .table-sm th {
|
||
padding: 8px;
|
||
}
|
||
|
||
.calculation-details {
|
||
background-color: #f8f9fa;
|
||
border: 1px solid var(--light-steel-blue);
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.text-primary {
|
||
color: var(--steel-blue) !important;
|
||
}
|
||
|
||
small {
|
||
font-size: 0.8rem;
|
||
color: #6c757d;
|
||
}
|
||
|
||
@media (max-width: 768px) {
|
||
.page-title {
|
||
font-size: 1.4rem;
|
||
}
|
||
|
||
.stat-number {
|
||
font-size: 1.6rem;
|
||
}
|
||
|
||
.accordion-button .row > div {
|
||
margin-bottom: 5px;
|
||
text-align: left !important;
|
||
}
|
||
}
|
||
</style>
|
||
{% endblock %} |