This commit is contained in:
Flatlogic Bot 2026-01-27 21:42:27 +00:00
parent 9e85e053cc
commit e5089ee366
21 changed files with 1167 additions and 821 deletions

View File

@ -0,0 +1,28 @@
# Generated by Django 5.2.7 on 2026-01-27 20:47
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('core', '0006_fleetunit_contract_number_and_more'),
]
operations = [
migrations.CreateModel(
name='MaintenancePart',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('part_name', models.CharField(max_length=255, verbose_name='Наименование (фильтр, масло и т.д.)')),
('article_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='Артикул')),
('quantity', models.CharField(max_length=50, verbose_name='Количество')),
('maintenance', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='used_parts', to='core.maintenance', verbose_name='ТО')),
],
options={
'verbose_name': 'Запчасть ТО',
'verbose_name_plural': 'Запчасти ТО',
},
),
]

View File

@ -216,4 +216,17 @@ class Document(models.Model):
verbose_name_plural = "Документы" verbose_name_plural = "Документы"
def __str__(self): def __str__(self):
return f"{self.get_doc_type_display()} - {self.uploaded_at}" return f"{self.get_doc_type_display()} - {self.uploaded_at}"
class MaintenancePart(models.Model):
maintenance = models.ForeignKey(Maintenance, on_delete=models.CASCADE, related_name='used_parts', verbose_name='ТО')
part_name = models.CharField(max_length=255, verbose_name='Наименование (фильтр, масло и т.д.)')
article_number = models.CharField(max_length=100, blank=True, null=True, verbose_name='Артикул')
quantity = models.CharField(max_length=50, verbose_name='Количество')
class Meta:
verbose_name = 'Запчасть ТО'
verbose_name_plural = 'Запчасти ТО'
def __str__(self):
return f'{self.part_name} ({self.quantity})'

View File

@ -6,11 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Fleet Manager{% endblock %}</title> <title>{% block title %}Fleet Manager{% endblock %}</title>
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=Montserrat:wght@600;700;800&display=swap" rel="stylesheet">
<!-- Bootstrap 5 CSS --> <!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons --> <!-- Bootstrap Icons -->
@ -21,48 +16,45 @@
{% block extra_css %}{% endblock %} {% block extra_css %}{% endblock %}
</head> </head>
<body class="bg-light"> <body>
<!-- Navbar --> <!-- Navbar -->
<nav class="navbar navbar-expand-lg navbar-dark bg-slate-900 sticky-top shadow-sm"> <nav class="navbar navbar-expand-lg sticky-top navbar-apple">
<div class="container"> <div class="container">
<a class="navbar-brand d-flex align-items-center" href="{% url 'index' %}"> <a class="navbar-brand d-flex align-items-center" href="{% url 'index' %}">
<i class="bi bi-truck-flatbed me-2 text-blue-500"></i> <i class="bi bi-apple me-2 text-primary"></i>
<span class="fw-bold text-uppercase tracking-wider">Fleet<span class="text-blue-500">Manager</span></span> <span class="fw-bold">Fleet Manager</span>
</a> </a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"> <button class="navbar-toggler border-0" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>
</button> </button>
<div class="collapse navbar-collapse" id="navbarSupportedContent"> <div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0 ms-lg-4"> <ul class="navbar-nav mx-auto mb-2 mb-lg-0">
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if request.resolver_match.url_name == 'index' %}active{% endif %}" href="{% url 'index' %}">Дашборд</a> <a class="nav-link {% if request.resolver_match.url_name == 'index' %}fw-bold{% endif %}" href="{% url 'index' %}">Дашборд</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if 'fleet' in request.path %}active{% endif %}" href="{% url 'fleet_list' %}">Техника</a> <a class="nav-link {% if 'fleet' in request.path %}fw-bold{% endif %}" href="{% url 'fleet_list' %}">Техника</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if 'maintenance' in request.path %}active{% endif %}" href="{% url 'maintenance_list' %}">ТО</a> <a class="nav-link {% if 'maintenance' in request.path %}fw-bold{% endif %}" href="{% url 'maintenance_list' %}">ТО</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if 'part-request' in request.path %}active{% endif %}" href="{% url 'part_request_list' %}">Заявки</a> <a class="nav-link {% if 'part-request' in request.path %}fw-bold{% endif %}" href="{% url 'part_request_list' %}">Заявки</a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link {% if 'supply' in request.path or 'supplier' in request.path %}active{% endif %}" href="{% url 'supply_list' %}">Снабжение</a> <a class="nav-link {% if 'supply' in request.path or 'supplier' in request.path %}fw-bold{% endif %}" href="{% url 'supply_list' %}">Снабжение</a>
</li> </li>
</ul> </ul>
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
<span class="text-white-50 me-3 d-none d-md-inline small">
{% if user.is_authenticated %}{{ user.get_full_name|default:user.username }}{% else %}Гость{% endif %}
</span>
<div class="dropdown"> <div class="dropdown">
<a href="#" class="d-block link-light text-decoration-none dropdown-toggle" id="dropdownUser1" data-bs-toggle="dropdown"> <a href="#" class="d-block link-dark text-decoration-none dropdown-toggle no-caret" id="dropdownUser1" data-bs-toggle="dropdown">
<img src="https://ui-avatars.com/api/?name={{ user.username }}&background=3b82f6&color=fff" alt="user" width="32" height="32" class="rounded-circle"> <img src="https://ui-avatars.com/api/?name={{ user.username }}&background=0071e3&color=fff" alt="user" width="28" height="28" class="rounded-circle">
</a> </a>
<ul class="dropdown-menu dropdown-menu-end text-small shadow border-0 mt-2" aria-labelledby="dropdownUser1"> <ul class="dropdown-menu dropdown-menu-end shadow-lg border-0 mt-3 rounded-4" aria-labelledby="dropdownUser1">
<li><a class="dropdown-item" href="/admin/">Админ-панель</a></li> <li class="px-3 py-2 small text-secondary-apple">Вошли как: <strong>{{ user.username }}</strong></li>
<li><a class="dropdown-item" href="#">Профиль</a></li> <li><hr class="dropdown-divider opacity-50"></li>
<li><hr class="dropdown-divider"></li> <li><a class="dropdown-item py-2" href="/admin/"><i class="bi bi-gear me-2"></i>Админ-панель</a></li>
<li><a class="dropdown-item" href="/admin/logout/">Выйти</a></li> <li><a class="dropdown-item py-2" href="/admin/logout/"><i class="bi bi-box-arrow-right me-2"></i>Выйти</a></li>
</ul> </ul>
</div> </div>
</div> </div>
@ -70,18 +62,23 @@
</div> </div>
</nav> </nav>
<main class="py-4"> <main class="container py-5">
{% block content %}{% endblock %} {% block content %}{% endblock %}
</main> </main>
<footer class="py-4 bg-white mt-auto border-top"> <footer class="py-5 mt-auto">
<div class="container text-center"> <div class="container text-center">
<p class="text-muted small mb-0">&copy; {% now "Y" %} Fleet Manager. Разработано для управления парком техники.</p> <p class="text-secondary-apple small mb-0">&copy; {% now "Y" %} Fleet Manager. Совершенство в деталях.</p>
</div> </div>
</footer> </footer>
<!-- Bootstrap 5 JS --> <!-- Bootstrap 5 JS -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<style>
.no-caret::after { display: none !important; }
.dropdown-item:hover { background-color: #f5f5f7; border-radius: 8px; }
.dropdown-menu { min-width: 200px; padding: 8px; }
</style>
{% block extra_js %}{% endblock %} {% block extra_js %}{% endblock %}
</body> </body>
</html> </html>

View File

@ -2,56 +2,61 @@
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="row mb-5">
<h3 class="fw-bold mb-0">Журнал поломок</h3> <div class="col-md-8">
<a href="{% url 'breakdown_add' %}" class="btn btn-danger rounded-pill px-4"> <h1 class="display-6 fw-bold mb-2">Журнал поломок</h1>
<i class="bi bi-exclamation-triangle me-2"></i>Сообщить о поломке <p class="text-secondary-apple">Учет неисправностей и контроль их устранения.</p>
</a> </div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<a href="{% url 'breakdown_add' %}" class="btn btn-danger">
<i class="bi bi-exclamation-triangle me-2"></i>Сообщить о поломке
</a>
</div>
</div> </div>
<div class="card border-0 shadow-sm"> <div class="card mb-4">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover align-middle mb-0"> <table class="table table-hover align-middle mb-0">
<thead class="bg-light"> <thead>
<tr> <tr>
<th class="border-0 px-4">Техника</th> <th class="ps-4">Техника</th>
<th class="border-0">Дата</th> <th>Дата</th>
<th class="border-0">Узел / Система</th> <th>Узел / Система</th>
<th class="border-0">Статус</th> <th>Статус</th>
<th class="border-0 text-end px-4">Действия</th> <th class="pe-4 text-end">Действия</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for b in breakdowns %} {% for b in breakdowns %}
<tr> <tr>
<td class="px-4"> <td class="ps-4">
<div class="fw-bold"><a href="{{ b.fleet_unit.get_absolute_url }}">{{ b.fleet_unit.name }}</a></div> <div class="fw-bold"><a href="{{ b.fleet_unit.get_absolute_url }}" class="text-decoration-none text-dark">{{ b.fleet_unit.name }}</a></div>
<div class="small text-muted">{{ b.fleet_unit.plate_number|default:b.fleet_unit.vin }}</div> <div class="small text-secondary-apple">{{ b.fleet_unit.plate_number|default:b.fleet_unit.vin }}</div>
</td> </td>
<td>{{ b.date }}</td> <td>{{ b.date|date:"d.m.Y" }}</td>
<td>{{ b.system_node }}</td> <td>{{ b.system_node }}</td>
<td> <td>
{% if b.status == 'reported' %} {% if b.status == 'reported' %}
<span class="badge bg-danger rounded-pill">Заявлено</span> <span class="badge bg-danger">Заявлено</span>
{% elif b.status == 'repaired' %} {% elif b.status == 'repaired' %}
<span class="badge bg-success rounded-pill">Отремонтировано</span> <span class="badge bg-success">Отремонтировано</span>
{% elif b.status == 'need_part' %} {% elif b.status == 'need_part' %}
<span class="badge bg-warning text-dark rounded-pill">Нужна деталь</span> <span class="badge bg-warning text-dark">Нужна деталь</span>
{% else %} {% else %}
<span class="badge bg-secondary rounded-pill">{{ b.status }}</span> <span class="badge bg-secondary">{{ b.get_status_display }}</span>
{% endif %} {% endif %}
</td> </td>
<td class="text-end px-4"> <td class="pe-4 text-end">
<div class="btn-group"> <div class="d-flex justify-content-end align-items-center">
<a href="{{ b.fleet_unit.get_absolute_url }}" class="btn btn-sm btn-outline-primary rounded-pill px-3 me-2">К технике</a> <a href="{{ b.fleet_unit.get_absolute_url }}" class="btn btn-sm btn-link text-decoration-none me-2">К технике</a>
{% if b.status == 'need_part' %} {% if b.status == 'need_part' %}
<a href="{% url 'part_request_add' %}?breakdown={{ b.pk }}" class="btn btn-sm btn-outline-warning rounded-pill px-3 me-2">Заказать запчасть</a> <a href="{% url 'part_request_add' %}?breakdown={{ b.pk }}" class="btn btn-sm btn-outline-warning py-1 px-3 small me-2">Заказать запчасть</a>
{% endif %} {% endif %}
{% if user.is_staff %} {% if user.is_staff %}
<form action="{% url 'breakdown_delete' b.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись о поломке?')"> <form action="{% url 'breakdown_delete' b.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись о поломке?')">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger rounded-pill px-2" title="Удалить"><i class="bi bi-trash"></i></button> <button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0"><i class="bi bi-trash"></i></button>
</form> </form>
{% endif %} {% endif %}
</div> </div>
@ -59,7 +64,7 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="5" class="text-center py-5 text-muted">Нет зафиксированных поломок</td> <td colspan="5" class="text-center py-5 text-secondary-apple">Нет зафиксированных поломок</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -67,4 +72,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,47 +2,56 @@
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container">
<div class="row"> <div class="row mb-5">
<div class="col-md-8">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-2">
<li class="breadcrumb-item"><a href="{% url 'fleet_list' %}" class="text-decoration-none text-secondary-apple">Техника</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ unit.name }}</li>
</ol>
</nav>
<h1 class="display-6 fw-bold mb-0">{{ unit.name }}</h1>
<p class="text-secondary-apple">{{ unit.model_name }} | {{ unit.plate_number|default:unit.vin }}</p>
</div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<span class="badge bg-{{ unit.get_status_color }} text-white px-3 py-2 fs-6">
{{ unit.get_status_display }}
</span>
</div>
</div>
<div class="row g-4">
<!-- Sidebar: Info & QR --> <!-- Sidebar: Info & QR -->
<div class="col-lg-4"> <div class="col-lg-4">
<div class="card shadow-sm border-0 mb-4"> <div class="card mb-4 overflow-hidden">
{% if unit.photo %} {% if unit.photo %}
<img src="{{ unit.photo.url }}" class="card-img-top" alt="{{ unit.name }}" style="height: 250px; object-fit: cover;"> <img src="{{ unit.photo.url }}" class="w-100" alt="{{ unit.name }}" style="height: 300px; object-fit: cover;">
{% else %} {% else %}
<div class="bg-light text-center py-5"> <div class="bg-light text-center py-5">
<i class="bi bi-truck text-muted display-1"></i> <i class="bi bi-truck text-secondary-apple display-1"></i>
</div> </div>
{% endif %} {% endif %}
<div class="card-body"> <div class="card-body p-4">
<div class="d-flex justify-content-between align-items-start mb-3"> <h5 class="fw-bold mb-4">Характеристики</h5>
<div>
<h4 class="fw-bold mb-0">{{ unit.name }}</h4>
<span class="text-muted small">{{ unit.model_name }}</span>
</div>
<span class="badge bg-{{ unit.get_status_color }} rounded-pill px-3 py-2">
{{ unit.get_status_display }}
</span>
</div>
<div class="row g-3 mb-4"> <div class="row g-3 mb-4">
<div class="col-6"> <div class="col-6">
<p class="text-muted small mb-0">Госномер</p> <p class="text-secondary-apple small mb-0">Госномер</p>
<p class="fw-bold mb-0">{{ unit.plate_number|default:"-" }}</p> <p class="fw-bold mb-0">{{ unit.plate_number|default:"-" }}</p>
</div> </div>
<div class="col-6"> <div class="col-6">
<p class="text-muted small mb-0">Год выпуска</p> <p class="text-secondary-apple small mb-0">Год выпуска</p>
<p class="fw-bold mb-0">{{ unit.year }}</p> <p class="fw-bold mb-0">{{ unit.year }}</p>
</div> </div>
<div class="col-12"> <div class="col-12 border-top pt-3">
<p class="text-muted small mb-0">VIN / Серийный номер</p> <p class="text-secondary-apple small mb-0">VIN / Серийный номер</p>
<p class="fw-bold mb-0">{{ unit.vin }}</p> <p class="fw-bold mb-0">{{ unit.vin }}</p>
</div> </div>
</div> </div>
{% if unit.insurance_company %} {% if unit.insurance_company %}
<div class="bg-light p-3 rounded mb-4"> <div class="p-3 rounded-4 bg-light mb-4">
<h6 class="fw-bold small mb-2"><i class="bi bi-shield-check me-2"></i>Страховка</h6> <h6 class="fw-bold small mb-2 text-primary"><i class="bi bi-shield-check me-2"></i>Страховка</h6>
<p class="small mb-1"><strong>Компания:</strong> {{ unit.insurance_company }}</p> <p class="small mb-1"><strong>Компания:</strong> {{ unit.insurance_company }}</p>
<p class="small mb-1"><strong>Полис:</strong> {{ unit.insurance_policy_number|default:"-" }}</p> <p class="small mb-1"><strong>Полис:</strong> {{ unit.insurance_policy_number|default:"-" }}</p>
<p class="small mb-0"><strong>Срок:</strong> {{ unit.insurance_start_date|date:"d.m.Y" }} — {{ unit.insurance_end_date|date:"d.m.Y" }}</p> <p class="small mb-0"><strong>Срок:</strong> {{ unit.insurance_start_date|date:"d.m.Y" }} — {{ unit.insurance_end_date|date:"d.m.Y" }}</p>
@ -50,89 +59,89 @@
{% endif %} {% endif %}
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<a href="{% url 'fleet_edit' unit.pk %}" class="btn btn-outline-primary rounded-pill">Редактировать</a> <a href="{% url 'fleet_edit' unit.pk %}" class="btn btn-outline-primary">Редактировать</a>
<a href="{% url 'breakdown_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-danger">Заявить о поломке</a>
{% if user.is_staff %} {% if user.is_staff %}
<form action="{% url 'fleet_delete' unit.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Вы уверены, что хотите удалить эту единицу техники? Все связанные данные будут удалены.')"> <form action="{% url 'fleet_delete' unit.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Вы уверены, что хотите удалить эту единицу техники?')">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-outline-danger rounded-pill w-100 mb-2">Удалить технику</button> <button type="submit" class="btn btn-link text-danger w-100 small text-decoration-none mt-2">Удалить технику</button>
</form> </form>
{% endif %} {% endif %}
<a href="{% url 'breakdown_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-danger rounded-pill">Заявить о поломке</a>
</div> </div>
</div> </div>
</div> </div>
<div class="card shadow-sm border-0 text-center p-4"> <div class="card p-4 text-center">
<h6 class="fw-bold mb-3">QR-код техники</h6> <h6 class="fw-bold mb-3">QR-код доступа</h6>
{% if unit.qr_code %} {% if unit.qr_code %}
<img src="{{ unit.qr_code.url }}" class="img-fluid mx-auto mb-3" style="max-width: 150px;" alt="QR Code"> <img src="{{ unit.qr_code.url }}" class="img-fluid mx-auto mb-3" style="max-width: 140px;" alt="QR Code">
{% else %} {% else %}
<div class="alert alert-light small">QR-код генерируется...</div> <div class="text-secondary-apple small py-3">Генерация...</div>
{% endif %} {% endif %}
<p class="text-muted small mb-0">Наклейте этот код на технику для быстрого доступа механика.</p> <p class="text-secondary-apple small mb-3">Для быстрого перехода механика к истории и заявкам.</p>
<button onclick="window.print()" class="btn btn-sm btn-link mt-2">Печать кода</button> <button onclick="window.print()" class="btn btn-sm btn-link text-decoration-none">Печать кода <i class="bi bi-printer ms-1"></i></button>
</div> </div>
</div> </div>
<!-- Main Content: Tabs --> <!-- Main Content: Tabs -->
<div class="col-lg-8"> <div class="col-lg-8">
<div class="card shadow-sm border-0"> <div class="card">
<div class="card-header bg-white border-0 pt-3"> <div class="card-header">
<ul class="nav nav-pills" id="fleetTab" role="tablist"> <ul class="nav nav-tabs border-0" id="fleetTab" role="tablist">
<li class="nav-item"> <li class="nav-item">
<button class="nav-link active rounded-pill px-4" id="history-tab" data-bs-toggle="tab" data-bs-target="#history" type="button">История ТО</button> <button class="nav-link active fw-bold border-0" id="history-tab" data-bs-toggle="tab" data-bs-target="#history" type="button">ТО</button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<button class="nav-link rounded-pill px-4" id="breakdowns-tab" data-bs-toggle="tab" data-bs-target="#breakdowns" type="button">Поломки</button> <button class="nav-link fw-bold border-0" id="breakdowns-tab" data-bs-toggle="tab" data-bs-target="#breakdowns" type="button">Поломки</button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<button class="nav-link rounded-pill px-4" id="parts-tab" data-bs-toggle="tab" data-bs-target="#parts" type="button">Заявки на запчасти</button> <button class="nav-link fw-bold border-0" id="parts-tab" data-bs-toggle="tab" data-bs-target="#parts" type="button">Заявки</button>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<button class="nav-link rounded-pill px-4" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply" type="button">Снабжение</button> <button class="nav-link fw-bold border-0" id="supply-tab" data-bs-toggle="tab" data-bs-target="#supply" type="button">Снабжение</button>
</li> </li>
</ul> </ul>
</div> </div>
<div class="card-body"> <div class="card-body p-4">
<div class="tab-content" id="fleetTabContent"> <div class="tab-content" id="fleetTabContent">
<!-- Maintenance Tab --> <!-- Maintenance Tab -->
<div class="tab-pane fade show active" id="history"> <div class="tab-pane fade show active" id="history">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-4">
<h6 class="fw-bold mb-0">Плановое и выполненное ТО</h6> <h6 class="fw-bold mb-0">История и планирование</h6>
<a href="{% url 'maintenance_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-primary rounded-pill">+ Запланировать</a> <a href="{% url 'maintenance_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-primary">+ Запланировать</a>
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-sm align-middle"> <table class="table align-middle">
<thead> <thead>
<tr> <tr>
<th>Тип</th> <th>Тип</th>
<th>Дата</th> <th>Дата</th>
<th>Статус</th> <th>Статус</th>
<th>Исполнитель</th> <th>Механик</th>
<th></th> <th class="text-end">Действие</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for m in maintenances %} {% for m in maintenances %}
<tr> <tr>
<td>{{ m.m_type }}</td> <td class="fw-bold">{{ m.m_type }}</td>
<td>{{ m.planned_date }}</td> <td>{{ m.planned_date }}</td>
<td><span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }} rounded-pill small">{{ m.get_status_display }}</span></td> <td><span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }}">{{ m.get_status_display }}</span></td>
<td>{{ m.mechanic|default:"-" }}</td> <td class="small">{{ m.mechanic|default:"-" }}</td>
<td class="text-end"> <td class="text-end">
<div class="btn-group"> <div class="d-flex justify-content-end align-items-center">
<a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-link text-primary p-0 me-2">Открыть</a> <a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-link text-decoration-none">Открыть</a>
{% if user.is_staff %} {% if user.is_staff %}
<form action="{% url 'maintenance_delete' m.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись ТО?')"> <form action="{% url 'maintenance_delete' m.pk %}" method="POST" class="d-inline ms-2">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-sm btn-link text-danger p-0"><i class="bi bi-trash"></i></button> <button type="submit" class="btn btn-sm text-danger border-0 bg-transparent"><i class="bi bi-trash"></i></button>
</form> </form>
{% endif %} {% endif %}
</div> </div>
</td> </td>
</tr> </tr>
{% empty %} {% empty %}
<tr><td colspan="5" class="text-center py-4 text-muted">Нет записей ТО</td></tr> <tr><td colspan="5" class="text-center py-5 text-secondary-apple">Записей не найдено</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -141,72 +150,72 @@
<!-- Breakdowns Tab --> <!-- Breakdowns Tab -->
<div class="tab-pane fade" id="breakdowns"> <div class="tab-pane fade" id="breakdowns">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-4">
<h6 class="fw-bold mb-0">Журнал поломок</h6> <h6 class="fw-bold mb-0">Журнал поломок</h6>
<a href="{% url 'breakdown_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-danger rounded-pill">+ Новая поломка</a> <a href="{% url 'breakdown_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-danger">+ Заявить</a>
</div> </div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush border-top">
{% for b in breakdowns %} {% for b in breakdowns %}
<div class="list-group-item px-0 py-3"> <div class="list-group-item px-0 py-3">
<div class="d-flex justify-content-between mb-1"> <div class="d-flex justify-content-between mb-2">
<h6 class="mb-0 fw-bold text-danger">{{ b.system_node }}</h6> <h6 class="mb-0 fw-bold text-danger">{{ b.system_node }}</h6>
<div> <div class="d-flex align-items-center">
<span class="badge bg-light text-dark border small me-2">{{ b.get_status_display }}</span> <span class="badge bg-light text-dark border-0 shadow-sm me-3">{{ b.get_status_display }}</span>
{% if user.is_staff %} {% if user.is_staff %}
<form action="{% url 'breakdown_delete' b.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись о поломке?')"> <form action="{% url 'breakdown_delete' b.pk %}" method="POST" class="d-inline">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-sm btn-link text-danger p-0"><i class="bi bi-trash"></i></button> <button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0"><i class="bi bi-trash"></i></button>
</form> </form>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<p class="small mb-1 text-muted">{{ b.date|date:"d.m.Y" }} — {{ b.description }}</p> <p class="small text-secondary-apple mb-3">{{ b.date|date:"d.m.Y" }} — {{ b.description }}</p>
<div class="d-flex align-items-center mt-2"> <div class="d-flex align-items-center">
<a href="{% url 'part_request_add' %}?breakdown={{ b.pk }}" class="btn btn-sm btn-outline-primary rounded-pill py-0 small me-2">+ Заказать запчасть</a> <a href="{% url 'part_request_add' %}?breakdown={{ b.pk }}" class="btn btn-sm btn-outline-primary py-1 px-3 small me-2">Заказать деталь</a>
{% if b.photo %}<span class="badge bg-light text-muted border small"><i class="bi bi-image me-1"></i>Есть фото</span>{% endif %} {% if b.photo %}<span class="text-secondary-apple small"><i class="bi bi-image me-1"></i>Фото приложено</span>{% endif %}
</div> </div>
</div> </div>
{% empty %} {% empty %}
<div class="text-center py-4 text-muted">Поломок не зафиксировано</div> <div class="text-center py-5 text-secondary-apple">Журнал пуст</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
<!-- Parts Tab --> <!-- Parts Tab -->
<div class="tab-pane fade" id="parts"> <div class="tab-pane fade" id="parts">
<div class="d-flex justify-content-between align-items-center mb-3"> <div class="d-flex justify-content-between align-items-center mb-4">
<h6 class="fw-bold mb-0">Заявки на запчасти</h6> <h6 class="fw-bold mb-0">Заявки на запчасти</h6>
<a href="{% url 'part_request_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-primary rounded-pill">+ Создать заявку</a> <a href="{% url 'part_request_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-primary">+ Новая заявка</a>
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-sm align-middle"> <table class="table align-middle">
<thead> <thead>
<tr> <tr>
<th>Деталь</th> <th>Деталь</th>
<th>Кол-во</th> <th>Кол-во</th>
<th>Статус</th> <th>Статус</th>
<th>Дата</th> <th>Дата</th>
<th></th> <th class="text-end"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for r in part_requests %} {% for r in part_requests %}
<tr> <tr>
<td>{{ r.part_name }}</td> <td class="fw-bold">{{ r.part_name }}</td>
<td>{{ r.quantity }}</td> <td>{{ r.quantity }}</td>
<td><span class="badge bg-light text-dark border rounded-pill small">{{ r.get_status_display }}</span></td> <td><span class="badge bg-light text-dark border-0 shadow-sm">{{ r.get_status_display }}</span></td>
<td class="small">{{ r.created_at|date:"d.m.Y" }}</td> <td class="small">{{ r.created_at|date:"d.m.Y" }}</td>
<td class="text-end"> <td class="text-end">
{% if user.is_staff %} {% if user.is_staff %}
<form action="{% url 'part_request_delete' r.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить заявку?')"> <form action="{% url 'part_request_delete' r.pk %}" method="POST" class="d-inline">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-sm btn-link text-danger p-0"><i class="bi bi-trash"></i></button> <button type="submit" class="btn btn-sm text-danger border-0 bg-transparent"><i class="bi bi-trash"></i></button>
</form> </form>
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
{% empty %} {% empty %}
<tr><td colspan="5" class="text-center py-4 text-muted">Нет заявок</td></tr> <tr><td colspan="5" class="text-center py-5 text-secondary-apple">Активных заявок нет</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
@ -215,85 +224,63 @@
<!-- Supply Tab --> <!-- Supply Tab -->
<div class="tab-pane fade" id="supply"> <div class="tab-pane fade" id="supply">
<h6 class="fw-bold mb-3">Данные по снабжению и поставщику</h6>
<div class="row g-4"> <div class="row g-4">
<div class="col-md-6"> <div class="col-md-7">
<div class="mb-3"> <div class="mb-4">
<p class="text-muted small mb-1">Номер договора (техника)</p> <h6 class="fw-bold mb-3">Договорные данные</h6>
<p class="fw-bold mb-0">{{ unit.contract_number|default:"-" }}</p> <div class="p-3 bg-light rounded-4">
<p class="text-secondary-apple small mb-1">Номер договора</p>
<p class="fw-bold mb-0 h5">{{ unit.contract_number|default:"Не указан" }}</p>
</div>
</div> </div>
{% if unit.supplier %} <div>
<p class="text-muted small mb-1">Поставщик / Контрагент</p> <h6 class="fw-bold mb-3">Поставщик</h6>
<p class="fw-bold mb-1">{{ unit.supplier.name }}</p> {% if unit.supplier %}
<div class="card p-3 border-0 shadow-sm bg-white">
<p class="text-muted small mb-1">Представитель</p> <p class="fw-bold mb-1">{{ unit.supplier.name }}</p>
<p class="small mb-1">{{ unit.supplier.representative_name|default:"-" }}</p> <p class="text-secondary-apple small mb-2">{{ unit.supplier.representative_name|default:"Контактное лицо не указано" }}</p>
<p class="small mb-1"><i class="bi bi-telephone me-2"></i>{{ unit.supplier.phone|default:"-" }}</p> <div class="d-flex flex-column gap-1">
<p class="small"><i class="bi bi-envelope me-2"></i>{{ unit.supplier.email|default:"-" }}</p> <a href="tel:{{ unit.supplier.phone }}" class="text-decoration-none small text-dark"><i class="bi bi-telephone text-primary me-2"></i>{{ unit.supplier.phone|default:"-" }}</a>
{% else %} <a href="mailto:{{ unit.supplier.email }}" class="text-decoration-none small text-dark"><i class="bi bi-envelope text-primary me-2"></i>{{ unit.supplier.email|default:"-" }}</a>
<p class="text-muted small">Поставщик не привязан</p> </div>
<p class="small text-muted">Вы можете выбрать поставщика в режиме редактирования техники.</p> </div>
{% endif %} {% else %}
<div class="alert alert-light border-0 small">Поставщик не привязан к данной технике.</div>
{% endif %}
</div>
</div> </div>
<div class="col-md-6 text-end"> <div class="col-md-5">
<p class="text-muted small mb-1">Документы на авто</p> <h6 class="fw-bold mb-3">Документация</h6>
{% if unit.vehicle_documents %} {% if unit.vehicle_documents %}
<a href="{{ unit.vehicle_documents.url }}" class="btn btn-outline-secondary btn-sm rounded-pill" target="_blank"> <a href="{{ unit.vehicle_documents.url }}" class="card p-4 text-center text-decoration-none text-dark hover-bg-light" target="_blank">
<i class="bi bi-file-earmark-text me-2"></i>Открыть документы <i class="bi bi-file-earmark-pdf text-danger display-4 mb-2"></i>
<div class="fw-bold">Посмотреть ПТС/СТС</div>
<div class="small text-secondary-apple">Загруженный файл</div>
</a> </a>
{% else %} {% else %}
<p class="text-muted small">Не загружены</p> <div class="text-center p-5 bg-light rounded-4 text-secondary-apple small">
Документы не загружены
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
<hr class="my-4">
<h6 class="fw-bold mb-3">Связанные заявки на запчасти</h6>
<div class="table-responsive">
<table class="table table-sm align-middle">
<thead>
<tr>
<th>Деталь</th>
<th>Статус</th>
<th>Дата</th>
<th></th>
</tr>
</thead>
<tbody>
{% for r in part_requests %}
<tr>
<td>{{ r.part_name }}</td>
<td><span class="badge bg-light text-dark border rounded-pill small">{{ r.get_status_display }}</span></td>
<td class="small">{{ r.created_at|date:"d.m.Y" }}</td>
<td class="text-end">
{% if user.is_staff %}
<form action="{% url 'part_request_delete' r.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить заявку?')">
{% csrf_token %}
<button type="submit" class="btn btn-sm btn-link text-danger p-0"><i class="bi bi-trash"></i></button>
</form>
{% endif %}
</td>
</tr>
{% empty %}
<tr><td colspan="4" class="text-center py-4 text-muted">Нет заявок</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="card shadow-sm border-0 mt-4"> <div class="card mt-4 p-4">
<div class="card-header bg-white fw-bold">Примечания</div> <h6 class="fw-bold mb-2">Заметки и примечания</h6>
<div class="card-body"> <p class="text-secondary-apple mb-0">{{ unit.notes|default:"Дополнительная информация отсутствует." }}</p>
<p class="mb-0">{{ unit.notes|default:"Нет примечаний" }}</p>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %}
<style>
.nav-tabs .nav-link { color: var(--apple-text-secondary); border-radius: 0; padding: 1rem 1.5rem; margin-right: 1rem; transition: all 0.2s; }
.nav-tabs .nav-link.active { color: var(--apple-blue); border-bottom: 2px solid var(--apple-blue) !important; background: transparent; }
.hover-bg-light:hover { background-color: #f5f5f7 !important; transform: translateY(-2px); }
</style>
{% endblock %}

View File

@ -1,122 +1,128 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
<div class="container py-5"> <div class="container">
<div class="row justify-content-center"> <div class="row justify-content-center">
<div class="col-lg-10"> <div class="col-lg-10">
<div class="card shadow-sm border-0"> <div class="card p-4 p-md-5">
<div class="card-header bg-white border-0 py-4"> <div class="text-center mb-5">
<h2 class="h4 mb-0 text-center"> <h1 class="fw-bold mb-2">
{% if object %}Редактировать технику{% else %}Добавить новую технику{% endif %} {% if object %}Редактировать технику{% else %}Новая единица техники{% endif %}
</h2> </h1>
<p class="text-secondary-apple">Заполните данные для корректного учета и планирования ТО.</p>
</div> </div>
<div class="card-body p-4">
<form method="post" enctype="multipart/form-data"> <form method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<!-- Section: Basic Info --> <!-- Section: Basic Info -->
<h5 class="mb-3 text-primary border-bottom pb-2">Основная информация</h5> <div class="mb-5">
<div class="row g-3 mb-4"> <h5 class="fw-bold mb-4 d-flex align-items-center"><i class="bi bi-info-circle me-2 text-primary"></i>Основная информация</h5>
<div class="row g-4">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Наименование *</label> <label class="form-label small fw-bold text-secondary-apple">Наименование *</label>
{{ form.name }} {{ form.name }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Модель *</label> <label class="form-label small fw-bold text-secondary-apple">Модель *</label>
{{ form.model_name }} {{ form.model_name }}
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Категория</label> <label class="form-label small fw-bold text-secondary-apple">Категория</label>
{{ form.category }} {{ form.category }}
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">VIN / Серийный номер *</label> <label class="form-label small fw-bold text-secondary-apple">VIN / Серийный номер *</label>
{{ form.vin }} {{ form.vin }}
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Госномер</label> <label class="form-label small fw-bold text-secondary-apple">Госномер</label>
{{ form.plate_number }} {{ form.plate_number }}
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Год выпуска *</label> <label class="form-label small fw-bold text-secondary-apple">Год выпуска *</label>
{{ form.year }} {{ form.year }}
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Дата ввода в эксплуатацию *</label> <label class="form-label small fw-bold text-secondary-apple">Дата ввода в эксплуатацию *</label>
{{ form.commissioning_date }} {{ form.commissioning_date }}
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<label class="form-label">Статус</label> <label class="form-label small fw-bold text-secondary-apple">Статус</label>
{{ form.status }} {{ form.status }}
</div> </div>
</div> </div>
</div>
<!-- Section: Insurance --> <!-- Section: Insurance -->
<h5 class="mb-3 text-primary border-bottom pb-2">Страховка</h5> <div class="mb-5">
<div class="row g-3 mb-4"> <h5 class="fw-bold mb-4 d-flex align-items-center"><i class="bi bi-shield-check me-2 text-primary"></i>Страхование</h5>
<div class="row g-4">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Страховая компания</label> <label class="form-label small fw-bold text-secondary-apple">Страховая компания</label>
{{ form.insurance_company }} {{ form.insurance_company }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Номер полиса</label> <label class="form-label small fw-bold text-secondary-apple">Номер полиса</label>
{{ form.insurance_policy_number }} {{ form.insurance_policy_number }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Дата начала</label> <label class="form-label small fw-bold text-secondary-apple">Дата начала</label>
{{ form.insurance_start_date }} {{ form.insurance_start_date }}
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Дата окончания</label> <label class="form-label small fw-bold text-secondary-apple">Дата окончания</label>
{{ form.insurance_end_date }} {{ form.insurance_end_date }}
</div> </div>
</div> </div>
</div>
<!-- Section: Supply --> <!-- Section: Supply -->
<h5 class="mb-3 text-primary border-bottom pb-2">Снабжение</h5> <div class="mb-5">
<div class="row g-3 mb-4"> <h5 class="fw-bold mb-4 d-flex align-items-center"><i class="bi bi-box-seam me-2 text-primary"></i>Снабжение</h5>
<div class="row g-4">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label d-flex justify-content-between"> <label class="form-label d-flex justify-content-between small fw-bold text-secondary-apple">
Поставщик / Контрагент Поставщик
<a href="{% url 'supplier_create' %}" target="_blank" class="small text-decoration-none"> <a href="{% url 'supplier_create' %}" target="_blank" class="fw-normal text-decoration-none">
<i class="bi bi-plus-circle"></i> Создать нового + Добавить в базу
</a> </a>
</label> </label>
{{ form.supplier }} {{ form.supplier }}
<div class="form-text">Выберите поставщика из списка или создайте нового (откроется в новой вкладке).</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Номер договора</label> <label class="form-label small fw-bold text-secondary-apple">Номер договора</label>
{{ form.contract_number }} {{ form.contract_number }}
<div class="form-text">Номер договора по данной единице техники.</div>
</div> </div>
<div class="col-md-6"> <div class="col-md-12">
<label class="form-label">Документы на авто (PDF/Image)</label> <label class="form-label small fw-bold text-secondary-apple">Документы на авто (PDF/Image)</label>
{{ form.vehicle_documents }} {{ form.vehicle_documents }}
</div> </div>
</div> </div>
</div>
<!-- Section: Media & Notes --> <!-- Section: Media & Notes -->
<h5 class="mb-3 text-primary border-bottom pb-2">Медиа и примечания</h5> <div class="mb-5">
<div class="row g-3 mb-4"> <h5 class="fw-bold mb-4 d-flex align-items-center"><i class="bi bi-camera me-2 text-primary"></i>Медиа</h5>
<div class="row g-4">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Фото техники</label> <label class="form-label small fw-bold text-secondary-apple">Фотографии техники</label>
{{ form.photo }} {{ form.photo }}
</div> </div>
<div class="col-12"> <div class="col-12">
<label class="form-label">Примечания</label> <label class="form-label small fw-bold text-secondary-apple">Примечания</label>
{{ form.notes }} {{ form.notes }}
</div> </div>
</div> </div>
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-end mt-5"> <div class="d-flex justify-content-end gap-3 mt-4 pt-4 border-top">
<a href="{% if object %}{% url 'fleet_detail' object.pk %}{% else %}{% url 'fleet_list' %}{% endif %}" class="btn btn-light px-4">Отмена</a> <a href="{% if object %}{% url 'fleet_detail' object.pk %}{% else %}{% url 'fleet_list' %}{% endif %}" class="btn btn-link text-decoration-none text-secondary-apple">Отмена</a>
<button type="submit" class="btn btn-primary px-5">Сохранить</button> <button type="submit" class="btn btn-primary px-5">Сохранить</button>
</div> </div>
</form> </form>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -3,95 +3,81 @@
{% block title %}Список техники | Fleet Manager{% endblock %} {% block title %}Список техники | Fleet Manager{% endblock %}
{% block content %} {% block content %}
<div class="d-flex flex-column flex-md-row justify-content-between align-items-start align-items-md-center mb-4 gap-3"> <div class="row mb-5">
<div> <div class="col-md-8">
<h2 class="mb-1">Парк техники</h2> <h1 class="display-6 fw-bold mb-2">Парк техники</h1>
<nav aria-label="breadcrumb"> <p class="text-secondary-apple">Управление и мониторинг каждой единицы оборудования.</p>
<ol class="breadcrumb mb-0"> </div>
<li class="breadcrumb-item"><a href="{% url 'index' %}" class="text-decoration-none">Дашборд</a></li> <div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<li class="breadcrumb-item active" aria-current="page">Техника</li> <a href="{% url 'fleet_add' %}" class="btn btn-primary">
</ol> <i class="bi bi-plus-lg me-2"></i>Добавить технику
</nav> </a>
</div> </div>
<a href="{% url 'fleet_add' %}" class="btn btn-primary">
<i class="bi bi-plus-lg me-2"></i>Добавить технику
</a>
</div> </div>
<!-- Filters --> <!-- Filters -->
<div class="card shadow-sm mb-4"> <div class="card p-4 mb-5">
<div class="card-body"> <form method="get" class="row g-3 align-items-center">
<form method="get" class="row g-3"> <div class="col-md-5">
<div class="col-md-5"> <div class="input-group">
<div class="input-group"> <span class="input-group-text bg-transparent border-end-0"><i class="bi bi-search text-secondary-apple"></i></span>
<span class="input-group-text bg-white border-end-0"><i class="bi bi-search text-muted"></i></span> <input type="text" name="search" class="form-control border-start-0 ps-0" placeholder="Поиск по названию или номеру..." value="{{ request.GET.search }}">
<input type="text" name="search" class="form-control border-start-0" placeholder="Поиск по названию или номеру..." value="{{ request.GET.search }}">
</div>
</div> </div>
<div class="col-md-4"> </div>
<select name="status" class="form-select" onchange="this.form.submit()"> <div class="col-md-4">
<option value="">Все статусы</option> <select name="status" class="form-select" onchange="this.form.submit()">
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>В работе</option> <option value="">Все статусы</option>
<option value="idle" {% if request.GET.status == 'idle' %}selected{% endif %}>Простаивает</option> <option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>В работе</option>
<option value="broken" {% if request.GET.status == 'broken' %}selected{% endif %}>Сломана</option> <option value="idle" {% if request.GET.status == 'idle' %}selected{% endif %}>Простаивает</option>
<option value="repair" {% if request.GET.status == 'repair' %}selected{% endif %}>В ремонте</option> <option value="broken" {% if request.GET.status == 'broken' %}selected{% endif %}>Сломана</option>
<option value="waiting_parts" {% if request.GET.status == 'waiting_parts' %}selected{% endif %}>Ждёт деталь</option> <option value="repair" {% if request.GET.status == 'repair' %}selected{% endif %}>В ремонте</option>
</select> <option value="waiting_parts" {% if request.GET.status == 'waiting_parts' %}selected{% endif %}>Ждёт деталь</option>
</div> </select>
<div class="col-md-3"> </div>
<button type="submit" class="btn btn-outline-secondary w-100">Применить</button> <div class="col-md-3">
</div> <button type="submit" class="btn btn-outline-primary w-100">Фильтровать</button>
</form> </div>
</div> </form>
</div> </div>
<div class="row g-4"> <div class="row g-4">
{% for unit in units %} {% for unit in units %}
<div class="col-12 col-md-6 col-xl-4"> <div class="col-12 col-md-6 col-lg-4">
<div class="card h-100 shadow-sm border-0"> <div class="card h-100">
<div class="row g-0 h-100"> {% if unit.photo %}
<div class="col-4"> <img src="{{ unit.photo.url }}" class="card-img-top object-fit-cover" alt="{{ unit.name }}" style="height: 200px;">
{% if unit.photo %} {% else %}
<img src="{{ unit.photo.url }}" class="img-fluid rounded-start h-100 object-fit-cover" alt="{{ unit.name }}" style="min-height: 160px;"> <div class="bg-light d-flex align-items-center justify-content-center" style="height: 200px;">
{% else %} <i class="bi bi-truck text-secondary-apple display-4"></i>
<div class="bg-light rounded-start h-100 d-flex align-items-center justify-content-center border-end" style="min-height: 160px;">
<i class="bi bi-truck text-muted display-6"></i>
</div>
{% endif %}
</div> </div>
<div class="col-8"> {% endif %}
<div class="card-body d-flex flex-column h-100"> <div class="card-body p-4">
<div class="d-flex justify-content-between align-items-start mb-2"> <div class="d-flex justify-content-between align-items-start mb-3">
<span class="badge bg-{{ unit.get_status_color }} bg-opacity-10 text-{{ unit.get_status_color }} border border-{{ unit.get_status_color }}"> <span class="badge bg-{{ unit.get_status_color }} text-white">
{{ unit.get_status_display }} {{ unit.get_status_display }}
</span> </span>
<span class="text-muted small">#{{ unit.pk }}</span> <span class="text-secondary-apple small">#{{ unit.pk }}</span>
</div> </div>
<h5 class="card-title mb-1"> <h5 class="fw-bold mb-1">
<a href="{{ unit.get_absolute_url }}" class="text-decoration-none text-slate-900">{{ unit.name }}</a> <a href="{{ unit.get_absolute_url }}" class="text-decoration-none text-dark">{{ unit.name }}</a>
</h5> </h5>
<p class="card-text text-muted small mb-3"> <p class="text-secondary-apple small mb-4">
{{ unit.model_name }} | {{ unit.plate_number|default:unit.vin }} {{ unit.model_name }} | {{ unit.plate_number|default:unit.vin }}
</p> </p>
<div class="mt-auto d-flex justify-content-between align-items-center"> <div class="d-flex justify-content-between align-items-center pt-3 border-top">
<div class="small text-muted"> <div class="small text-secondary-apple">
<i class="bi bi-calendar3 me-1"></i>{{ unit.year }} <i class="bi bi-calendar3 me-1"></i>{{ unit.year }}
</div>
<a href="{{ unit.get_absolute_url }}" class="btn btn-sm btn-outline-primary">Подробнее</a>
</div>
</div> </div>
<a href="{{ unit.get_absolute_url }}" class="btn btn-sm btn-link text-decoration-none p-0">Подробнее <i class="bi bi-chevron-right small"></i></a>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% empty %} {% empty %}
<div class="col-12 text-center py-5"> <div class="col-12 text-center py-5">
<div class="mb-3"> <i class="bi bi-inbox text-secondary-apple display-1 mb-3"></i>
<i class="bi bi-inbox text-muted display-1"></i> <h4 class="fw-bold">Техника не найдена</h4>
</div> <p class="text-secondary-apple">Попробуйте изменить параметры поиска.</p>
<h4 class="text-muted">Техника не найдена</h4>
<p class="text-muted">Попробуйте изменить параметры поиска или добавить новую единицу.</p>
<a href="{% url 'fleet_add' %}" class="btn btn-primary mt-2">Добавить технику</a>
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
@ -99,21 +85,19 @@
<!-- Pagination --> <!-- Pagination -->
{% if is_paginated %} {% if is_paginated %}
<nav class="mt-5"> <nav class="mt-5">
<ul class="pagination justify-content-center"> <ul class="pagination justify-content-center border-0">
{% if page_obj.has_previous %} {% if page_obj.has_previous %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.previous_page_number }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Назад</a></li> <li class="page-item"><a class="page-link border-0 rounded-3 mx-1 text-dark" href="?page={{ page_obj.previous_page_number }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"><i class="bi bi-chevron-left"></i></a></li>
{% endif %} {% endif %}
{% for i in paginator.page_range %} {% for i in paginator.page_range %}
<li class="page-item {% if page_obj.number == i %}active{% endif %}"> <li class="page-item"><a class="page-link border-0 rounded-3 mx-1 {% if page_obj.number == i %}bg-primary text-white{% else %}text-dark{% endif %}" href="?page={{ i }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">{{ i }}</a></li>
<a class="page-link" href="?page={{ i }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">{{ i }}</a>
</li>
{% endfor %} {% endfor %}
{% if page_obj.has_next %} {% if page_obj.has_next %}
<li class="page-item"><a class="page-link" href="?page={{ page_obj.next_page_number }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}">Вперед</a></li> <li class="page-item"><a class="page-link border-0 rounded-3 mx-1 text-dark" href="?page={{ page_obj.next_page_number }}{% if request.GET.status %}&status={{ request.GET.status }}{% endif %}{% if request.GET.search %}&search={{ request.GET.search }}{% endif %}"><i class="bi bi-chevron-right"></i></a></li>
{% endif %} {% endif %}
</ul> </ul>
</nav> </nav>
{% endif %} {% endif %}
{% endblock %} {% endblock %}

View File

@ -2,59 +2,50 @@
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container">
<div class="row mb-5 text-center">
<div class="col-12">
<h1 class="display-5 fw-bold mb-2">Обзор парка</h1>
<p class="text-secondary-apple">Вся информация о вашей технике в одном месте.</p>
</div>
</div>
<!-- Stats Row --> <!-- Stats Row -->
<div class="row g-4 mb-4"> <div class="row g-4 mb-5">
<div class="col-md-3"> <div class="col-md-3">
<div class="card border-0 shadow-sm bg-slate-900 text-white p-3 h-100"> <div class="card p-4 h-100 text-center">
<div class="d-flex align-items-center"> <div class="stat-icon mx-auto mb-3 d-flex align-items-center justify-content-center" style="width: 56px; height: 56px;">
<div class="rounded-circle bg-blue-500 p-3 me-3"> <i class="bi bi-truck fs-3"></i>
<i class="bi bi-truck fs-4"></i>
</div>
<div>
<h3 class="fw-bold mb-0">{{ total_units }}</h3>
<span class="text-white-50 small">Всего техники</span>
</div>
</div> </div>
<h3 class="fw-bold mb-1">{{ total_units }}</h3>
<span class="text-secondary-apple small">Всего техники</span>
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<div class="card border-0 shadow-sm p-3 h-100"> <div class="card p-4 h-100 text-center">
<div class="d-flex align-items-center"> <div class="stat-icon mx-auto mb-3 d-flex align-items-center justify-content-center bg-success-subtle text-success" style="width: 56px; height: 56px;">
<div class="rounded-circle bg-success-subtle p-3 me-3 text-success"> <i class="bi bi-check-circle fs-3"></i>
<i class="bi bi-check-circle fs-4"></i>
</div>
<div>
<h3 class="fw-bold mb-0 text-success">{{ active_units }}</h3>
<span class="text-muted small">В работе</span>
</div>
</div> </div>
<h3 class="fw-bold mb-1 text-success">{{ active_units }}</h3>
<span class="text-secondary-apple small">В работе</span>
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<div class="card border-0 shadow-sm p-3 h-100"> <div class="card p-4 h-100 text-center">
<div class="d-flex align-items-center"> <div class="stat-icon mx-auto mb-3 d-flex align-items-center justify-content-center bg-danger-subtle text-danger" style="width: 56px; height: 56px;">
<div class="rounded-circle bg-danger-subtle p-3 me-3 text-danger"> <i class="bi bi-exclamation-triangle fs-3"></i>
<i class="bi bi-exclamation-triangle fs-4"></i>
</div>
<div>
<h3 class="fw-bold mb-0 text-danger">{{ broken_units }}</h3>
<span class="text-muted small">Сломано</span>
</div>
</div> </div>
<h3 class="fw-bold mb-1 text-danger">{{ broken_units }}</h3>
<span class="text-secondary-apple small">Сломано</span>
</div> </div>
</div> </div>
<div class="col-md-3"> <div class="col-md-3">
<div class="card border-0 shadow-sm p-3 h-100"> <div class="card p-4 h-100 text-center">
<div class="d-flex align-items-center"> <div class="stat-icon mx-auto mb-3 d-flex align-items-center justify-content-center bg-warning-subtle text-warning" style="width: 56px; height: 56px;">
<div class="rounded-circle bg-warning-subtle p-3 me-3 text-warning"> <i class="bi bi-wrench fs-3"></i>
<i class="bi bi-wrench fs-4"></i>
</div>
<div>
<h3 class="fw-bold mb-0 text-warning">{{ repair_units }}</h3>
<span class="text-muted small">В ремонте</span>
</div>
</div> </div>
<h3 class="fw-bold mb-1 text-warning">{{ repair_units }}</h3>
<span class="text-secondary-apple small">В ремонте</span>
</div> </div>
</div> </div>
</div> </div>
@ -62,53 +53,53 @@
<div class="row g-4"> <div class="row g-4">
<!-- Recent Activities --> <!-- Recent Activities -->
<div class="col-lg-8"> <div class="col-lg-8">
<div class="card border-0 shadow-sm mb-4"> <div class="card mb-4">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<h6 class="fw-bold mb-0">Ближайшие ТО</h6> <h6 class="fw-bold mb-0">Ближайшие ТО</h6>
<a href="{% url 'maintenance_list' %}" class="btn btn-sm btn-link">Все ТО</a> <a href="{% url 'maintenance_list' %}" class="btn btn-sm btn-link text-decoration-none p-0">Все ТО <i class="bi bi-chevron-right small"></i></a>
</div> </div>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover align-middle mb-0"> <table class="table table-hover align-middle mb-0">
<thead class="bg-light"> <thead>
<tr> <tr>
<th>Техника</th> <th class="ps-4">Техника</th>
<th>Тип</th> <th>Тип</th>
<th>Дата</th> <th>Дата</th>
<th>Статус</th> <th class="pe-4 text-end">Статус</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for m in recent_maintenances %} {% for m in recent_maintenances %}
<tr> <tr>
<td><a href="{{ m.fleet_unit.get_absolute_url }}" class="text-decoration-none fw-bold">{{ m.fleet_unit.name }}</a></td> <td class="ps-4"><a href="{{ m.fleet_unit.get_absolute_url }}" class="text-decoration-none fw-bold text-dark">{{ m.fleet_unit.name }}</a></td>
<td>{{ m.m_type }}</td> <td>{{ m.m_type }}</td>
<td>{{ m.planned_date }}</td> <td>{{ m.planned_date }}</td>
<td><span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }} rounded-pill small">{{ m.get_status_display }}</span></td> <td class="pe-4 text-end"><span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }}">{{ m.get_status_display }}</span></td>
</tr> </tr>
{% empty %} {% empty %}
<tr><td colspan="4" class="text-center py-4 text-muted">Нет запланированных ТО</td></tr> <tr><td colspan="4" class="text-center py-5 text-secondary-apple">Нет запланированных ТО</td></tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div> </div>
<div class="card border-0 shadow-sm"> <div class="card">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<h6 class="fw-bold mb-0">Последние поломки</h6> <h6 class="fw-bold mb-0">Последние поломки</h6>
<a href="{% url 'breakdown_list' %}" class="btn btn-sm btn-link">Весь журнал</a> <a href="{% url 'breakdown_list' %}" class="btn btn-sm btn-link text-decoration-none p-0">Весь журнал <i class="bi bi-chevron-right small"></i></a>
</div> </div>
<div class="list-group list-group-flush"> <div class="list-group list-group-flush">
{% for b in recent_breakdowns %} {% for b in recent_breakdowns %}
<div class="list-group-item d-flex justify-content-between align-items-center py-3"> <div class="list-group-item d-flex justify-content-between align-items-center">
<div> <div>
<div class="fw-bold text-danger">{{ b.system_node }}</div> <div class="fw-bold text-danger">{{ b.system_node }}</div>
<div class="small text-muted">{{ b.fleet_unit.name }} — {{ b.date|date:"d.m.Y" }}</div> <div class="small text-secondary-apple">{{ b.fleet_unit.name }} — {{ b.date|date:"d.m.Y" }}</div>
</div> </div>
<span class="badge bg-light text-dark border">{{ b.get_status_display }}</span> <span class="badge bg-light text-dark border-0 shadow-sm">{{ b.get_status_display }}</span>
</div> </div>
{% empty %} {% empty %}
<div class="list-group-item text-center py-4 text-muted">Поломок не зафиксировано</div> <div class="list-group-item text-center py-5 text-secondary-apple">Поломок не зафиксировано</div>
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
@ -116,45 +107,39 @@
<!-- Quick Actions & Search --> <!-- Quick Actions & Search -->
<div class="col-lg-4"> <div class="col-lg-4">
<div class="card border-0 shadow-sm mb-4"> <div class="card mb-4 p-4">
<div class="card-body"> <h6 class="fw-bold mb-3">Поиск</h6>
<h6 class="fw-bold mb-3">Быстрый поиск</h6> <form action="{% url 'fleet_list' %}" method="get">
<form action="{% url 'fleet_list' %}" method="get"> <div class="input-group">
<div class="input-group mb-3"> <span class="input-group-text bg-transparent border-end-0"><i class="bi bi-search text-secondary-apple"></i></span>
<input type="text" name="search" class="form-control border-end-0" placeholder="Госномер или VIN..."> <input type="text" name="search" class="form-control border-start-0 ps-0" placeholder="Госномер или VIN...">
<button class="btn btn-outline-secondary border-start-0" type="submit">
<i class="bi bi-search"></i>
</button>
</div>
</form>
</div>
</div>
<div class="card border-0 shadow-sm mb-4">
<div class="card-body">
<h6 class="fw-bold mb-3">Быстрые действия</h6>
<div class="d-grid gap-2">
<a href="{% url 'fleet_add' %}" class="btn btn-primary py-2 rounded-pill">
<i class="bi bi-plus-lg me-2"></i>Добавить технику
</a>
<a href="{% url 'maintenance_add' %}" class="btn btn-outline-primary py-2 rounded-pill">
<i class="bi bi-calendar-plus me-2"></i>Запланировать ТО
</a>
<a href="{% url 'breakdown_add' %}" class="btn btn-outline-danger py-2 rounded-pill">
<i class="bi bi-exclamation-circle me-2"></i>Заявить о поломке
</a>
</div> </div>
</form>
</div>
<div class="card mb-4 p-4">
<h6 class="fw-bold mb-4">Действия</h6>
<div class="d-grid gap-3">
<a href="{% url 'fleet_add' %}" class="btn btn-primary">
<i class="bi bi-plus-lg me-2"></i>Добавить технику
</a>
<a href="{% url 'maintenance_add' %}" class="btn btn-outline-primary">
<i class="bi bi-calendar-plus me-2"></i>Запланировать ТО
</a>
<a href="{% url 'breakdown_add' %}" class="btn btn-outline-danger">
<i class="bi bi-exclamation-circle me-2"></i>Заявить о поломке
</a>
</div> </div>
</div> </div>
<div class="card border-0 shadow-sm bg-blue-50"> <div class="card p-4 border-0" style="background: linear-gradient(135deg, #eef2f3 0%, #8e9eab 100%);">
<div class="card-body"> <h6 class="fw-bold mb-2">Мобильная версия</h6>
<h6 class="fw-bold text-blue-900 mb-2">Мобильное приложение</h6> <p class="small text-secondary-apple mb-3">Используйте камеру телефона для сканирования QR-кодов на технике для быстрого доступа.</p>
<p class="small text-blue-800 mb-3">Приложение оптимизировано для работы механиков с телефона. Используйте QR-коды для быстрого доступа к чек-листам.</p> <div class="text-center">
<i class="bi bi-phone-vibrate text-blue-500 display-4 d-block text-center"></i> <i class="bi bi-phone fs-1"></i>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,132 +2,200 @@
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container">
<div class="row"> <div class="row mb-5">
<div class="col-md-8">
<nav aria-label="breadcrumb">
<ol class="breadcrumb mb-2">
<li class="breadcrumb-item"><a href="{% url 'maintenance_list' %}" class="text-decoration-none text-secondary-apple">ТО</a></li>
<li class="breadcrumb-item active" aria-current="page">{{ maintenance.m_type }}</li>
</ol>
</nav>
<h1 class="display-6 fw-bold mb-0">{{ maintenance.m_type }}</h1>
<p class="text-secondary-apple">{{ maintenance.fleet_unit.name }} | {{ maintenance.planned_date }}</p>
</div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<span class="badge bg-{{ maintenance.status|yesno:'success,warning,secondary' }} text-white px-3 py-2 fs-6">
{{ maintenance.get_status_display }}
</span>
</div>
</div>
<div class="row g-4">
<div class="col-lg-8"> <div class="col-lg-8">
<div class="card shadow-sm mb-4 border-0"> <div class="card p-4 mb-4">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center"> <h5 class="fw-bold mb-4">Детали обслуживания</h5>
<h5 class="mb-0 fw-bold"> <div class="row mb-4">
<i class="bi bi-tools text-primary me-2"></i> <div class="col-md-6">
{{ maintenance.m_type }} - {{ maintenance.fleet_unit.name }} <p class="text-secondary-apple small mb-1">Техника</p>
</h5> <p class="fw-bold mb-3"><a href="{{ maintenance.fleet_unit.get_absolute_url }}" class="text-dark">{{ maintenance.fleet_unit }}</a></p>
<span class="badge bg-{{ maintenance.status|yesno:'success,warning,secondary' }} rounded-pill px-3">
{% if maintenance.status == 'planned' %}Планируется <p class="text-secondary-apple small mb-1">Плановая дата</p>
{% elif maintenance.status == 'in_progress' %}В процессе <p class="fw-bold mb-3">{{ maintenance.planned_date }}</p>
{% else %}Выполнено{% endif %} </div>
</span> <div class="col-md-6">
<p class="text-secondary-apple small mb-1">Исполнитель</p>
<p class="fw-bold mb-3">{{ maintenance.mechanic|default:"Не назначен" }}</p>
<p class="text-secondary-apple small mb-1">Плановый пробег</p>
<p class="fw-bold mb-3">{{ maintenance.planned_runtime }}</p>
</div>
</div> </div>
<div class="card-body">
<div class="row mb-4">
<div class="col-md-6">
<p class="text-muted small mb-1">Техника</p>
<p class="fw-bold mb-3"><a href="{{ maintenance.fleet_unit.get_absolute_url }}">{{ maintenance.fleet_unit }}</a></p>
<p class="text-muted small mb-1">Плановая дата</p>
<p class="fw-bold mb-3">{{ maintenance.planned_date }}</p>
</div>
<div class="col-md-6">
<p class="text-muted small mb-1">Исполнитель</p>
<p class="fw-bold mb-3">{{ maintenance.mechanic|default:"Не назначен" }}</p>
<p class="text-muted small mb-1">Плановый пробег/моточасы</p>
<p class="fw-bold mb-3">{{ maintenance.planned_runtime }}</p>
</div>
</div>
<hr class="my-4"> <hr class="my-4 opacity-50">
<h6 class="fw-bold mb-3">Чек-лист операций</h6> <h5 class="fw-bold mb-4">Чек-лист операций</h5>
<div class="list-group list-group-flush border rounded mb-4"> <div class="list-group list-group-flush mb-5">
{% for item in maintenance.checklist %} {% for item in maintenance.checklist %}
<div class="list-group-item d-flex align-items-center py-3"> <div class="list-group-item d-flex align-items-center py-3 border-0 border-bottom">
<form action="{% url 'maintenance_process' maintenance.pk %}" method="post" class="d-flex align-items-center w-100"> <form action="{% url 'maintenance_process' maintenance.pk %}" method="post" class="d-flex align-items-center w-100">
{% csrf_token %}
<input type="hidden" name="task_index" value="{{ forloop.counter0 }}">
<div class="form-check">
<input class="form-check-input" type="checkbox" name="done" value="true"
id="task_{{ forloop.counter0 }}"
{% if item.done %}checked{% endif %}
onchange="this.form.submit()">
<label class="form-check-label {% if item.done %}text-decoration-line-through text-muted{% endif %}" for="task_{{ forloop.counter0 }}">
{{ item.task }}
</label>
</div>
</form>
</div>
{% empty %}
<div class="list-group-item text-muted text-center py-4">Чек-лист пуст</div>
{% endfor %}
</div>
{% if maintenance.status != 'completed' %}
<div class="mt-4 pt-3 border-top">
<h6 class="fw-bold mb-3">Завершение ТО</h6>
<form action="{% url 'maintenance_complete' maintenance.pk %}" method="post" enctype="multipart/form-data">
{% csrf_token %} {% csrf_token %}
<div class="row g-3"> <input type="hidden" name="task_index" value="{{ forloop.counter0 }}">
<div class="col-md-6"> <div class="form-check">
<label class="form-label small fw-bold">Фактическая дата</label> <input class="form-check-input border-secondary-apple" type="checkbox" name="done" value="true"
<input type="date" name="actual_date" class="form-control" value="{% now 'Y-m-d' %}" required> id="task_{{ forloop.counter0 }}"
</div> {% if item.done %}checked{% endif %}
<div class="col-md-6"> onchange="this.form.submit()">
<label class="form-label small fw-bold">Фактический пробег/моточасы</label> <label class="form-check-label {% if item.done %}text-decoration-line-through text-secondary-apple{% endif %}" for="task_{{ forloop.counter0 }}">
<input type="number" name="actual_runtime" class="form-control" required> {{ item.task }}
</div> </label>
<div class="col-12">
<label class="form-label small fw-bold">Использованные запчасти</label>
<textarea name="parts_used" class="form-control" rows="2"></textarea>
</div>
<div class="col-12">
<button type="submit" class="btn btn-success w-100 py-2 fw-bold">
<i class="bi bi-check-circle me-2"></i>Завершить ТО и обновить статус техники
</button>
</div>
</div> </div>
</form> </form>
</div> </div>
{% else %} {% empty %}
<div class="mt-4 pt-3 border-top"> <div class="text-center py-4 text-secondary-apple">Чек-лист пуст</div>
<div class="alert alert-success d-flex align-items-center"> {% endfor %}
<i class="bi bi-info-circle-fill me-2"></i> </div>
<div>ТО успешно завершено {{ maintenance.actual_date }}</div>
</div> <h5 class="fw-bold mb-4">Расходные материалы</h5>
<div class="row"> <div class="table-responsive mb-4">
<table class="table align-middle">
<thead>
<tr>
<th>Наименование</th>
<th>Артикул</th>
<th>Кол-во</th>
<th width="50"></th>
</tr>
</thead>
<tbody>
{% for part in maintenance.used_parts.all %}
<tr>
<td class="fw-bold">{{ part.part_name }}</td>
<td class="small">{{ part.article_number|default:"-" }}</td>
<td>{{ part.quantity }}</td>
<td class="text-end">
<form action="{% url 'maintenance_part_delete' part.pk %}" method="post" class="d-inline">
{% csrf_token %}
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0" onclick="return confirm('Удалить?')">
<i class="bi bi-trash"></i>
</button>
</form>
</td>
</tr>
{% empty %}
<tr>
<td colspan="4" class="text-center text-secondary-apple py-4">Расходники не добавлены</td>
</tr>
{% endfor %}
{% if maintenance.status != 'completed' %}
<tr class="table-light border-0">
<td colspan="4" class="p-3">
<form action="{% url 'maintenance_part_add' maintenance.pk %}" method="post" class="row g-2">
{% csrf_token %}
<div class="col-md-5">{{ part_form.part_name }}</div>
<div class="col-md-3">{{ part_form.article_number }}</div>
<div class="col-md-4">
<div class="input-group">
{{ part_form.quantity }}
<button type="submit" class="btn btn-primary btn-sm">
<i class="bi bi-plus-lg"></i>
</button>
</div>
</div>
</form>
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
{% if maintenance.status != 'completed' %}
<div class="mt-5 p-4 bg-light rounded-4">
<h5 class="fw-bold mb-4">Завершение работ</h5>
<form action="{% url 'maintenance_complete' maintenance.pk %}" method="post" enctype="multipart/form-data">
{% csrf_token %}
<div class="row g-4">
<div class="col-md-6"> <div class="col-md-6">
<p class="text-muted small mb-1">Фактический пробег/моточасы</p> <label class="form-label small fw-bold">Фактическая дата</label>
<p class="fw-bold">{{ maintenance.actual_runtime }}</p> <input type="date" name="actual_date" class="form-control" value="{% now 'Y-m-d' %}" required>
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<p class="text-muted small mb-1">Запчасти</p> <label class="form-label small fw-bold">Фактический пробег</label>
<p>{{ maintenance.parts_used|default:"-" }}</p> <input type="number" name="actual_runtime" class="form-control" required>
</div>
<div class="col-12">
<label class="form-label small fw-bold">Примечания по работам</label>
<textarea name="parts_used" class="form-control" rows="3" placeholder="Опишите дополнительные детали..."></textarea>
</div>
<div class="col-12">
<button type="submit" class="btn btn-primary w-100 py-3 fw-bold">
<i class="bi bi-check-circle me-2"></i>Завершить техническое обслуживание
</button>
</div> </div>
</div> </div>
<a href="{% url 'maintenance_pdf' maintenance.pk %}" class="btn btn-outline-primary btn-sm mt-2"> </form>
<i class="bi bi-file-pdf me-1"></i>Скачать Акт ТО (PDF) </div>
</a> {% else %}
<div class="mt-5 pt-4 border-top">
<div class="alert alert-success rounded-4 d-flex align-items-center mb-4">
<i class="bi bi-check2-circle fs-4 me-3"></i>
<div>
<div class="fw-bold">ТО успешно выполнено</div>
<div class="small">Дата: {{ maintenance.actual_date }} | Пробег: {{ maintenance.actual_runtime }}</div>
</div>
</div>
{% if maintenance.parts_used %}
<div class="mb-4">
<p class="text-secondary-apple small mb-1">Доп. работы</p>
<p class="fw-bold">{{ maintenance.parts_used }}</p>
</div> </div>
{% endif %} {% endif %}
<a href="{% url 'maintenance_pdf' maintenance.pk %}" class="btn btn-outline-primary">
<i class="bi bi-file-earmark-pdf me-2"></i>Скачать PDF-отчет
</a>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
<div class="col-lg-4"> <div class="col-lg-4">
<div class="card shadow-sm border-0 mb-4"> <div class="card p-4 mb-4">
<div class="card-header bg-white fw-bold py-3">Примечания</div> <h6 class="fw-bold mb-3">Техника</h6>
<div class="card-body"> <div class="d-flex align-items-center mb-4">
<p class="mb-0">{{ maintenance.notes|default:"Нет примечаний" }}</p> {% if maintenance.fleet_unit.photo %}
<img src="{{ maintenance.fleet_unit.photo.url }}" class="rounded-3 me-3" style="width: 60px; height: 60px; object-fit: cover;">
{% endif %}
<div>
<div class="fw-bold">{{ maintenance.fleet_unit.name }}</div>
<div class="small text-secondary-apple">{{ maintenance.fleet_unit.model_name }}</div>
</div>
</div>
<div class="row g-2 small">
<div class="col-5 text-secondary-apple">VIN:</div>
<div class="col-7 fw-bold">{{ maintenance.fleet_unit.vin }}</div>
<div class="col-5 text-secondary-apple">Госномер:</div>
<div class="col-7 fw-bold">{{ maintenance.fleet_unit.plate_number|default:"-" }}</div>
</div> </div>
</div> </div>
{% if maintenance.fleet_unit.photo %} <div class="card p-4">
<div class="card shadow-sm border-0"> <h6 class="fw-bold mb-3">Исходные примечания</h6>
<img src="{{ maintenance.fleet_unit.photo.url }}" class="card-img-top" alt="{{ maintenance.fleet_unit.name }}"> <p class="text-secondary-apple small mb-0">{{ maintenance.notes|default:"Примечания отсутствуют" }}</p>
<div class="card-body">
<p class="card-text small text-muted text-center">{{ maintenance.fleet_unit.name }}</p>
</div>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,51 +2,54 @@
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="row mb-5">
<h3 class="fw-bold mb-0">Техническое обслуживание</h3> <div class="col-md-8">
<a href="{% url 'maintenance_add' %}" class="btn btn-primary rounded-pill px-4"> <h1 class="display-6 fw-bold mb-2">Техническое обслуживание</h1>
<i class="bi bi-plus-lg me-2"></i>Запланировать ТО <p class="text-secondary-apple">Планирование и контроль регламентных работ.</p>
</a> </div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<a href="{% url 'maintenance_add' %}" class="btn btn-primary">
<i class="bi bi-plus-lg me-2"></i>Запланировать ТО
</a>
</div>
</div> </div>
<div class="card border-0 shadow-sm"> <div class="card mb-4">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover align-middle mb-0"> <table class="table table-hover align-middle mb-0">
<thead class="bg-light"> <thead>
<tr> <tr>
<th class="border-0 px-4">Техника</th> <th class="ps-4">Техника</th>
<th class="border-0">Тип</th> <th>Тип</th>
<th class="border-0">Плановая дата</th> <th>Дата</th>
<th class="border-0">Статус</th> <th>Статус</th>
<th class="border-0">Исполнитель</th> <th>Механик</th>
<th class="border-0 text-end px-4">Действия</th> <th class="pe-4 text-end">Действия</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for m in maintenances %} {% for m in maintenances %}
<tr> <tr>
<td class="px-4"> <td class="ps-4">
<div class="fw-bold"><a href="{{ m.fleet_unit.get_absolute_url }}">{{ m.fleet_unit.name }}</a></div> <div class="fw-bold"><a href="{{ m.fleet_unit.get_absolute_url }}" class="text-decoration-none text-dark">{{ m.fleet_unit.name }}</a></div>
<div class="small text-muted">{{ m.fleet_unit.plate_number|default:m.fleet_unit.vin }}</div> <div class="small text-secondary-apple">{{ m.fleet_unit.plate_number|default:m.fleet_unit.vin }}</div>
</td> </td>
<td><span class="badge bg-light text-dark border">{{ m.get_m_type_display }}</span></td> <td>{{ m.get_m_type_display }}</td>
<td>{{ m.planned_date }}</td> <td>{{ m.planned_date }}</td>
<td> <td>
<span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }} rounded-pill"> <span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }}">
{% if m.status == 'planned' %}Планируется {{ m.get_status_display }}
{% elif m.status == 'in_progress' %}В процессе
{% else %}Выполнено{% endif %}
</span> </span>
</td> </td>
<td>{{ m.mechanic|default:"-" }}</td> <td class="small">{{ m.mechanic|default:"-" }}</td>
<td class="text-end px-4"> <td class="pe-4 text-end">
<div class="btn-group"> <div class="d-flex justify-content-end align-items-center">
<a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-outline-primary rounded-pill px-3 me-2">Открыть</a> <a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-link text-decoration-none me-2">Открыть</a>
{% if user.is_staff %} {% if user.is_staff %}
<form action="{% url 'maintenance_delete' m.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись ТО?')"> <form action="{% url 'maintenance_delete' m.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись ТО?')">
{% csrf_token %} {% csrf_token %}
<button type="submit" class="btn btn-sm btn-outline-danger rounded-pill px-2" title="Удалить"><i class="bi bi-trash"></i></button> <button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0"><i class="bi bi-trash"></i></button>
</form> </form>
{% endif %} {% endif %}
</div> </div>
@ -54,7 +57,7 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="6" class="text-center py-5 text-muted">Нет запланированных ТО</td> <td colspan="6" class="text-center py-5 text-secondary-apple">Нет запланированных ТО</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -62,4 +65,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -2,37 +2,42 @@
{% load static %} {% load static %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="row mb-5">
<h3 class="fw-bold mb-0">Заявки на запчасти</h3> <div class="col-md-8">
<a href="{% url 'part_request_add' %}" class="btn btn-primary rounded-pill px-4"> <h1 class="display-6 fw-bold mb-2">Заявки на запчасти</h1>
<i class="bi bi-plus-lg me-2"></i>Новая заявка <p class="text-secondary-apple">Отслеживание поставок и управление запасами.</p>
</a> </div>
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
<a href="{% url 'part_request_add' %}" class="btn btn-primary">
<i class="bi bi-plus-lg me-2"></i>Новая заявка
</a>
</div>
</div> </div>
<div class="card border-0 shadow-sm"> <div class="card mb-4">
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-hover align-middle mb-0"> <table class="table table-hover align-middle mb-0">
<thead class="bg-light"> <thead>
<tr> <tr>
<th class="border-0 px-4">Техника</th> <th class="ps-4">Техника</th>
<th class="border-0">Деталь</th> <th>Деталь</th>
<th class="border-0">Артикул</th> <th>Артикул</th>
<th class="border-0">Кол-во</th> <th>Кол-во</th>
<th class="border-0">Статус</th> <th>Статус</th>
<th class="border-0">Дата создания</th> <th>Дата</th>
<th class="border-0 text-end px-4">Действия</th> <th class="pe-4 text-end">Действия</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for r in requests %} {% for r in requests %}
<tr> <tr>
<td class="px-4"> <td class="ps-4">
<div class="fw-bold">{{ r.fleet_unit.name }}</div> <div class="fw-bold">{{ r.fleet_unit.name }}</div>
<div class="small text-muted">{{ r.fleet_unit.plate_number|default:r.fleet_unit.vin }}</div> <div class="small text-secondary-apple">{{ r.fleet_unit.plate_number|default:r.fleet_unit.vin }}</div>
</td> </td>
<td>{{ r.part_name }}</td> <td class="fw-bold text-dark">{{ r.part_name }}</td>
<td>{{ r.article_number|default:"-" }}</td> <td class="small">{{ r.article_number|default:"-" }}</td>
<td>{{ r.quantity }}</td> <td>{{ r.quantity }}</td>
<td> <td>
{% if r.status == 'draft' %}<span class="badge bg-secondary">Черновик</span> {% if r.status == 'draft' %}<span class="badge bg-secondary">Черновик</span>
@ -41,24 +46,24 @@
{% elif r.status == 'delivered' %}<span class="badge bg-success">Доставлено</span> {% elif r.status == 'delivered' %}<span class="badge bg-success">Доставлено</span>
{% endif %} {% endif %}
</td> </td>
<td class="small">{{ r.created_at|date:"d.m.Y H:i" }}</td> <td class="small text-secondary-apple">{{ r.created_at|date:"d.m.Y" }}</td>
<td class="text-end px-4"> <td class="pe-4 text-end">
<div class="btn-group"> <div class="dropdown">
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle rounded-pill px-3" data-bs-toggle="dropdown"> <button class="btn btn-sm btn-link text-decoration-none dropdown-toggle no-caret" type="button" data-bs-toggle="dropdown">
Действие <i class="bi bi-three-dots"></i>
</button> </button>
<ul class="dropdown-menu dropdown-menu-end shadow-sm border-0"> <ul class="dropdown-menu dropdown-menu-end shadow-lg border-0 rounded-3">
<li><a class="dropdown-item" href="#">Изменить статус</a></li> <li><a class="dropdown-item py-2" href="#"><i class="bi bi-pencil me-2"></i>Изменить</a></li>
<li><a class="dropdown-item" href="#">Экспорт Excel</a></li> <li><a class="dropdown-item py-2" href="#"><i class="bi bi-check2-circle me-2"></i>Обновить статус</a></li>
<li><hr class="dropdown-divider"></li> <li><hr class="dropdown-divider opacity-50"></li>
<li><a class="dropdown-item text-danger" href="#">Удалить</a></li> <li><a class="dropdown-item py-2 text-danger" href="#"><i class="bi bi-trash me-2"></i>Удалить</a></li>
</ul> </ul>
</div> </div>
</td> </td>
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="7" class="text-center py-5 text-muted">Нет активных заявок</td> <td colspan="7" class="text-center py-5 text-secondary-apple">Нет активных заявок</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -66,4 +71,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -1,116 +1,112 @@
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
<div class="container-fluid py-4"> <div class="container">
<div class="d-flex justify-content-between align-items-center mb-4"> <div class="row mb-5">
<h1 class="h2">Снабжение</h1> <div class="col-12 text-center">
<h1 class="display-5 fw-bold mb-2">Снабжение</h1>
<p class="text-secondary-apple">Управление закупками и работа с контрагентами.</p>
</div>
</div> </div>
<div class="row"> <div class="row g-4">
<!-- Part Requests Section --> <!-- Part Requests Section -->
<div class="col-lg-8"> <div class="col-lg-8">
<div class="card shadow-sm mb-4"> <div class="card mb-4">
<div class="card-header bg-white py-3"> <div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Заявки на запчасти</h5> <h5 class="fw-bold mb-0">Заявки на запчасти</h5>
</div> </div>
<div class="card-body"> <div class="table-responsive">
<div class="table-responsive"> <table class="table table-hover align-middle mb-0">
<table class="table table-hover align-middle"> <thead>
<thead class="table-light"> <tr>
<tr> <th class="ps-4">Дата</th>
<th>Дата</th> <th>Техника</th>
<th>Техника</th> <th>Деталь</th>
<th>Деталь</th> <th>Статус</th>
<th>Кол-во</th> <th class="pe-4 text-end">Действия</th>
<th>Статус</th> </tr>
<th>Действия</th> </thead>
</tr> <tbody>
</thead> {% for req in requests %}
<tbody> <tr>
{% for req in requests %} <td class="ps-4 small text-secondary-apple">{{ req.created_at|date:"d.m.Y" }}</td>
<tr> <td>
<td>{{ req.created_at|date:"d.m.Y" }}</td> <a href="{% url 'fleet_detail' req.fleet_unit.pk %}" class="text-decoration-none fw-bold text-dark">
<td> {{ req.fleet_unit.name }}
<a href="{% url 'fleet_detail' req.fleet_unit.pk %}" class="text-decoration-none"> </a>
{{ req.fleet_unit.name }} </td>
</a> <td>{{ req.part_name }} <span class="text-secondary-apple small">x{{ req.quantity }}</span></td>
</td> <td>
<td>{{ req.part_name }}</td> <span class="badge bg-light text-dark border-0 shadow-sm">
<td>{{ req.quantity }}</td> {{ req.get_status_display }}
<td> </span>
<span class="badge rounded-pill bg-{{ req.status|yesno:'info,secondary' }}"> </td>
{{ req.get_status_display }} <td class="pe-4 text-end">
</span> <div class="d-flex justify-content-end align-items-center">
</td> <a href="#" class="btn btn-sm btn-link text-decoration-none me-2">Управление</a>
<td> {% if user.is_staff %}
<div class="btn-group"> <form action="{% url 'part_request_delete' req.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить заявку?')">
<a href="#" class="btn btn-sm btn-outline-primary">Управление</a> {% csrf_token %}
{% if user.is_staff %} <button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0">
<form action="{% url 'part_request_delete' req.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить заявку?')"> <i class="bi bi-trash"></i>
{% csrf_token %} </button>
<button type="submit" class="btn btn-sm btn-outline-danger ms-1" title="Удалить"> </form>
<i class="bi bi-trash"></i> {% endif %}
</button> </div>
</form> </td>
{% endif %} </tr>
</div> {% empty %}
</td> <tr>
</tr> <td colspan="5" class="text-center py-5 text-secondary-apple">Активных заявок нет</td>
{% empty %} </tr>
<tr> {% endfor %}
<td colspan="6" class="text-center py-4">Нет активных заявок</td> </tbody>
</tr> </table>
{% endfor %}
</tbody>
</table>
</div>
</div> </div>
</div> </div>
</div> </div>
<!-- Suppliers Section --> <!-- Suppliers Section -->
<div class="col-lg-4"> <div class="col-lg-4">
<div class="card shadow-sm"> <div class="card">
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center"> <div class="card-header d-flex justify-content-between align-items-center">
<h5 class="card-title mb-0">Поставщики</h5> <h5 class="fw-bold mb-0">Контрагенты</h5>
<a href="{% url 'supplier_create' %}" class="btn btn-sm btn-primary"> <a href="{% url 'supplier_create' %}" class="btn btn-sm btn-primary">
<i class="bi bi-plus-lg"></i> Добавить <i class="bi bi-plus-lg"></i>
</a> </a>
</div> </div>
<div class="card-body"> <div class="list-group list-group-flush">
<div class="list-group list-group-flush"> {% for supplier in suppliers %}
{% for supplier in suppliers %} <div class="list-group-item">
<div class="list-group-item px-0 py-3"> <div class="d-flex w-100 justify-content-between align-items-start mb-2">
<div class="d-flex w-100 justify-content-between align-items-center"> <h6 class="fw-bold mb-0 text-dark">{{ supplier.name }}</h6>
<h6 class="mb-1">{{ supplier.name }}</h6> <div class="d-flex gap-2">
<div class="btn-group"> <a href="{% url 'supplier_edit' supplier.pk %}" class="text-secondary-apple" title="Редактировать">
<a href="{% url 'supplier_edit' supplier.pk %}" class="btn btn-sm btn-link p-0 text-muted me-2" title="Редактировать"> <i class="bi bi-pencil-square"></i>
<i class="bi bi-pencil-square"></i> </a>
</a> {% if user.is_staff %}
{% if user.is_staff %} <form action="{% url 'supplier_delete' supplier.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить поставщика?')">
<form action="{% url 'supplier_delete' supplier.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить поставщика?')"> {% csrf_token %}
{% csrf_token %} <button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0" title="Удалить">
<button type="submit" class="btn btn-sm btn-link p-0 text-danger" title="Удалить"> <i class="bi bi-trash"></i>
<i class="bi bi-trash"></i> </button>
</button> </form>
</form> {% endif %}
{% endif %}
</div>
</div> </div>
<p class="mb-1 small text-muted">
<strong>Менеджер:</strong> {{ supplier.representative_name|default:"-" }}<br>
<strong>Тел:</strong> {{ supplier.phone|default:"-" }}<br>
<strong>Email:</strong> {{ supplier.email|default:"-" }}<br>
<strong>Договор:</strong> {{ supplier.contract_number|default:"-" }}
</p>
</div> </div>
{% empty %} <div class="small text-secondary-apple">
<div class="text-center py-3 text-muted">Список поставщиков пуст</div> <div class="mb-1"><i class="bi bi-person me-2"></i>{{ supplier.representative_name|default:"-" }}</div>
{% endfor %} <div class="mb-1"><i class="bi bi-telephone me-2"></i>{{ supplier.phone|default:"-" }}</div>
<div class="mb-1"><i class="bi bi-file-text me-2"></i>Договор: {{ supplier.contract_number|default:"-" }}</div>
</div>
</div> </div>
{% empty %}
<div class="text-center py-5 text-secondary-apple">Список пуст</div>
{% endfor %}
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -20,6 +20,8 @@ urlpatterns = [
path('maintenance/<int:pk>/process/', views.MaintenanceProcessView.as_view(), name='maintenance_process'), path('maintenance/<int:pk>/process/', views.MaintenanceProcessView.as_view(), name='maintenance_process'),
path('maintenance/<int:pk>/complete/', views.MaintenanceCompleteView.as_view(), name='maintenance_complete'), path('maintenance/<int:pk>/complete/', views.MaintenanceCompleteView.as_view(), name='maintenance_complete'),
path('maintenance/<int:pk>/pdf/', views.MaintenancePDFView.as_view(), name='maintenance_pdf'), path('maintenance/<int:pk>/pdf/', views.MaintenancePDFView.as_view(), name='maintenance_pdf'),
path('maintenance/<int:pk>/part/add/', views.MaintenancePartAddView.as_view(), name='maintenance_part_add'),
path('maintenance-part/<int:pk>/delete/', views.MaintenancePartDeleteView.as_view(), name='maintenance_part_delete'),
# Breakdown # Breakdown
path('breakdown/', views.BreakdownListView.as_view(), name='breakdown_list'), path('breakdown/', views.BreakdownListView.as_view(), name='breakdown_list'),

View File

@ -11,7 +11,7 @@ from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4 from reportlab.lib.pagesizes import A4
from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfbase.ttfonts import TTFont
from .models import FleetUnit, Maintenance, Breakdown, PartRequest, Category, Document, Supplier from .models import FleetUnit, Maintenance, Breakdown, PartRequest, Category, Document, Supplier, MaintenancePart
# Mixins # Mixins
class StaffRequiredMixin(UserPassesTestMixin): class StaffRequiredMixin(UserPassesTestMixin):
@ -73,6 +73,16 @@ class MaintenanceForm(forms.ModelForm):
'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), 'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}),
} }
class MaintenancePartForm(forms.ModelForm):
class Meta:
model = MaintenancePart
fields = ['part_name', 'article_number', 'quantity']
widgets = {
'part_name': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': 'Например: Масло 5W-30'}),
'article_number': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': 'Артикул'}),
'quantity': forms.TextInput(attrs={'class': 'form-control form-control-sm', 'placeholder': 'Кол-во'}),
}
class BreakdownForm(forms.ModelForm): class BreakdownForm(forms.ModelForm):
class Meta: class Meta:
model = Breakdown model = Breakdown
@ -181,6 +191,11 @@ class MaintenanceDetailView(DetailView):
template_name = 'core/maintenance_detail.html' template_name = 'core/maintenance_detail.html'
context_object_name = 'maintenance' context_object_name = 'maintenance'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['part_form'] = MaintenancePartForm()
return context
class MaintenanceCreateView(CreateView): class MaintenanceCreateView(CreateView):
model = Maintenance model = Maintenance
template_name = 'core/maintenance_form.html' template_name = 'core/maintenance_form.html'
@ -248,9 +263,27 @@ class MaintenanceCompleteView(View):
return redirect('maintenance_detail', pk=pk) return redirect('maintenance_detail', pk=pk)
class MaintenancePartAddView(View):
def post(self, request, pk):
maintenance = get_object_or_404(Maintenance, pk=pk)
form = MaintenancePartForm(request.POST)
if form.is_valid():
part = form.save(commit=False)
part.maintenance = maintenance
part.save()
return redirect('maintenance_detail', pk=pk)
class MaintenancePartDeleteView(View):
def post(self, request, pk):
part = get_object_or_404(MaintenancePart, pk=pk)
maintenance_pk = part.maintenance.pk
part.delete()
return redirect('maintenance_detail', pk=maintenance_pk)
class MaintenancePDFView(View): class MaintenancePDFView(View):
def get(self, request, pk): def get(self, request, pk):
maintenance = get_object_or_404(Maintenance, pk=pk) maintenance = get_object_or_404(Maintenance, pk=pk)
unit = maintenance.fleet_unit
buffer = BytesIO() buffer = BytesIO()
p = canvas.Canvas(buffer, pagesize=A4) p = canvas.Canvas(buffer, pagesize=A4)
@ -262,19 +295,103 @@ class MaintenancePDFView(View):
except: except:
pass # Fallback to default if font not found pass # Fallback to default if font not found
p.drawString(100, 800, f"Акт технического обслуживания - {maintenance.m_type}") # Header
p.drawString(100, 780, f"Техника: {maintenance.fleet_unit.name}") p.setFont('LiberationSans', 16)
p.drawString(100, 760, f"Дата: {maintenance.actual_date}") p.drawString(100, 800, f"Акт технического обслуживания: {maintenance.m_type}")
p.drawString(100, 740, f"Наработка: {maintenance.actual_runtime}")
y = 700 p.setFont('LiberationSans', 10)
p.drawString(100, y, "Чек-лист:") p.drawString(100, 780, f"Статус ТО: {maintenance.get_status_display()}")
p.setFont('LiberationSans', 12)
p.setStrokeColorRGB(0.7, 0.7, 0.7)
p.line(100, 770, 500, 770)
# Unit Info (The Header requested by user)
y = 750
p.setFont('LiberationSans', 11)
p.drawString(100, y, f"ТЕХНИКА: {unit.name}")
y -= 15
p.drawString(100, y, f"Модель: {unit.model_name}")
y -= 15
p.drawString(100, y, f"VIN / Серийный номер: {unit.vin}")
y -= 15
p.drawString(100, y, f"Госномер: {unit.plate_number or '-'}")
y -= 15
p.drawString(100, y, f"Год выпуска: {unit.year}")
y -= 25
p.line(100, y, 500, y)
y -= 15
# Execution info
p.drawString(100, y, f"Дата проведения: {maintenance.actual_date or '-'}")
y -= 15
p.drawString(100, y, f"Наработка (моточасы/км): {maintenance.actual_runtime or '-'}")
y -= 15
p.drawString(100, y, f"Исполнитель: {maintenance.mechanic.get_full_name() or maintenance.mechanic.username if maintenance.mechanic else '-'}")
y -= 30
p.setFont('LiberationSans', 14)
p.drawString(100, y, "Чек-лист операций:")
y -= 20 y -= 20
p.setFont('LiberationSans', 10)
for item in maintenance.checklist: for item in maintenance.checklist:
status = "[x]" if item['done'] else "[ ]" status = "[x]" if item['done'] else "[ ]"
p.drawString(120, y, f"{status} {item['task']}") p.drawString(120, y, f"{status} {item['task']}")
y -= 20 y -= 15
if y < 100:
p.showPage()
y = 800
p.setFont('LiberationSans', 10)
# Used Parts and Fluids (The specific list requested by user)
y -= 20
if y < 150:
p.showPage()
y = 800
p.setFont('LiberationSans', 14)
p.drawString(100, y, "Использованные запчасти и жидкости:")
y -= 20
# Table Header for parts
p.setFont('LiberationSans', 10)
p.drawString(100, y, "Наименование")
p.drawString(300, y, "Артикул")
p.drawString(450, y, "Кол-во")
y -= 5
p.line(100, y, 500, y)
y -= 15
parts = maintenance.used_parts.all()
if parts:
for part in parts:
p.drawString(100, y, f"{part.part_name}")
p.drawString(300, y, f"{part.article_number or '-'}")
p.drawString(450, y, f"{part.quantity}")
y -= 15
if y < 100:
p.showPage()
y = 800
else:
p.drawString(100, y, "Нет записей")
y -= 15
y -= 20
if maintenance.notes:
p.setFont('LiberationSans', 11)
p.drawString(100, y, "Примечания:")
y -= 15
p.setFont('LiberationSans', 9)
# Simple text wrapping for notes
lines = maintenance.notes.split('\n')
for line in lines:
p.drawString(120, y, line[:80])
y -= 12
if y < 100:
p.showPage()
y = 800
p.showPage() p.showPage()
p.save() p.save()

View File

@ -1,116 +1,191 @@
:root { :root {
--bg-slate-900: #1e293b; --apple-bg: #f5f5f7;
--text-blue-500: #3b82f6; --apple-card-bg: #ffffff;
--primary: #3b82f6; --apple-blue: #0071e3;
--secondary: #64748b; --apple-text: #1d1d1f;
--success: #10b981; --apple-text-secondary: #86868b;
--danger: #ef4444; --apple-border: #d2d2d7;
--warning: #f59e0b; --primary: #0071e3;
--info: #06b6d4; --secondary: #86868b;
--success: #28cd41;
--danger: #ff3b30;
--warning: #ff9f0a;
--info: #55bef0;
} }
body { body {
font-family: 'Inter', sans-serif; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
color: #334155; background-color: var(--apple-bg);
color: var(--apple-text);
-webkit-font-smoothing: antialiased;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-family: 'Montserrat', sans-serif; font-weight: 600;
font-weight: 700; letter-spacing: -0.022em;
} }
.bg-slate-900 { /* Glassmorphism Navbar */
background-color: var(--bg-slate-900) !important; .navbar-apple {
background-color: rgba(255, 255, 255, 0.8) !important;
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: saturate(180%) blur(20px);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
} }
.text-blue-500 { .navbar-apple .nav-link {
color: var(--text-blue-500) !important; color: var(--apple-text) !important;
font-size: 0.9rem;
font-weight: 400;
} }
.tracking-wider { .navbar-apple .nav-link:hover {
letter-spacing: 0.05em; color: var(--apple-blue) !important;
} }
/* Card Styling */ .navbar-apple .navbar-brand {
color: var(--apple-text) !important;
font-weight: 600;
}
/* Apple Cards */
.card { .card {
background: var(--apple-card-bg);
border: none; border: none;
border-radius: 12px; border-radius: 20px;
transition: transform 0.2s ease, box-shadow 0.2s ease; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease;
overflow: hidden;
} }
.card:hover { .card:hover {
transform: translateY(-2px); transform: scale(1.01);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
} }
/* Stat Cards */ .card-header {
.stat-card { background: transparent;
padding: 1.5rem; border-bottom: 1px solid rgba(0, 0, 0, 0.05);
display: flex; padding: 1.25rem 1.5rem;
align-items: center;
} }
.stat-icon { /* Buttons */
width: 48px; .btn {
height: 48px;
border-radius: 12px; border-radius: 12px;
display: flex; padding: 0.6rem 1.2rem;
align-items: center; font-size: 0.95rem;
justify-content: center; font-weight: 500;
font-size: 1.5rem; transition: all 0.2s ease;
margin-right: 1rem; }
.btn-primary {
background-color: var(--apple-blue);
border-color: var(--apple-blue);
}
.btn-primary:hover {
background-color: #0077ed;
border-color: #0077ed;
}
.btn-outline-primary {
color: var(--apple-blue);
border-color: var(--apple-blue);
}
/* Form Controls */
.form-control, .form-select {
background-color: #fbfbfd;
border: 1px solid var(--apple-border);
border-radius: 12px;
padding: 0.75rem 1rem;
font-size: 1rem;
color: var(--apple-text);
}
.form-control:focus {
background-color: #ffffff;
border-color: var(--apple-blue);
box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1);
} }
/* Badges */ /* Badges */
.badge { .badge {
padding: 0.5em 0.8em; border-radius: 8px;
font-weight: 500; padding: 0.4em 0.8em;
border-radius: 6px; font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.02em;
} }
/* Hero Section */ /* Stat Cards Customization */
.hero-gradient { .stat-card {
background: linear-gradient(135deg, #1e293b 0%, #334155 100%); border-radius: 20px;
color: white; }
padding: 3rem 0;
border-radius: 16px; .stat-icon {
background: rgba(0, 113, 227, 0.1);
color: var(--apple-blue);
border-radius: 14px;
}
/* Hero section */
.hero-section {
background: #ffffff;
border-radius: 24px;
padding: 4rem 2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
text-align: center;
} }
/* Form Styling */ .text-secondary-apple {
.form-control, .form-select { color: var(--apple-text-secondary) !important;
border-radius: 8px;
padding: 0.625rem 0.75rem;
border: 1px solid #e2e8f0;
} }
.form-control:focus, .form-select:focus { /* Table styling */
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); .table {
border-color: #3b82f6; --bs-table-bg: transparent;
} }
.btn { .table thead th {
border-radius: 8px; font-weight: 600;
padding: 0.625rem 1.25rem; color: var(--apple-text-secondary);
font-weight: 500; text-transform: uppercase;
font-size: 0.75rem;
border-bottom: 1px solid var(--apple-border);
} }
.btn-primary { /* List group */
background-color: var(--primary); .list-group-item {
border-color: var(--primary); border-color: rgba(0, 0, 0, 0.05);
padding: 1rem 1.5rem;
} }
.btn-primary:hover { .list-group-item:first-child {
background-color: #2563eb; border-top-left-radius: 20px;
border-color: #2563eb; border-top-right-radius: 20px;
} }
/* Mobile adjustments */ .list-group-item:last-child {
@media (max-width: 768px) { border-bottom-left-radius: 20px;
.stat-card { border-bottom-right-radius: 20px;
padding: 1rem; }
}
.hero-gradient { /* Custom Scrollbar for modern look */
padding: 2rem 1rem; ::-webkit-scrollbar {
} width: 8px;
} }
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #d2d2d7;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #86868b;
}

View File

@ -1,116 +1,191 @@
:root { :root {
--bg-slate-900: #1e293b; --apple-bg: #f5f5f7;
--text-blue-500: #3b82f6; --apple-card-bg: #ffffff;
--primary: #3b82f6; --apple-blue: #0071e3;
--secondary: #64748b; --apple-text: #1d1d1f;
--success: #10b981; --apple-text-secondary: #86868b;
--danger: #ef4444; --apple-border: #d2d2d7;
--warning: #f59e0b; --primary: #0071e3;
--info: #06b6d4; --secondary: #86868b;
--success: #28cd41;
--danger: #ff3b30;
--warning: #ff9f0a;
--info: #55bef0;
} }
body { body {
font-family: 'Inter', sans-serif; font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
color: #334155; background-color: var(--apple-bg);
color: var(--apple-text);
-webkit-font-smoothing: antialiased;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-family: 'Montserrat', sans-serif; font-weight: 600;
font-weight: 700; letter-spacing: -0.022em;
} }
.bg-slate-900 { /* Glassmorphism Navbar */
background-color: var(--bg-slate-900) !important; .navbar-apple {
background-color: rgba(255, 255, 255, 0.8) !important;
backdrop-filter: saturate(180%) blur(20px);
-webkit-backdrop-filter: saturate(180%) blur(20px);
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
} }
.text-blue-500 { .navbar-apple .nav-link {
color: var(--text-blue-500) !important; color: var(--apple-text) !important;
font-size: 0.9rem;
font-weight: 400;
} }
.tracking-wider { .navbar-apple .nav-link:hover {
letter-spacing: 0.05em; color: var(--apple-blue) !important;
} }
/* Card Styling */ .navbar-apple .navbar-brand {
color: var(--apple-text) !important;
font-weight: 600;
}
/* Apple Cards */
.card { .card {
background: var(--apple-card-bg);
border: none; border: none;
border-radius: 12px; border-radius: 20px;
transition: transform 0.2s ease, box-shadow 0.2s ease; box-shadow: 0 4px 24px rgba(0, 0, 0, 0.04);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1), box-shadow 0.3s ease;
overflow: hidden;
} }
.card:hover { .card:hover {
transform: translateY(-2px); transform: scale(1.01);
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
} }
/* Stat Cards */ .card-header {
.stat-card { background: transparent;
padding: 1.5rem; border-bottom: 1px solid rgba(0, 0, 0, 0.05);
display: flex; padding: 1.25rem 1.5rem;
align-items: center;
} }
.stat-icon { /* Buttons */
width: 48px; .btn {
height: 48px;
border-radius: 12px; border-radius: 12px;
display: flex; padding: 0.6rem 1.2rem;
align-items: center; font-size: 0.95rem;
justify-content: center; font-weight: 500;
font-size: 1.5rem; transition: all 0.2s ease;
margin-right: 1rem; }
.btn-primary {
background-color: var(--apple-blue);
border-color: var(--apple-blue);
}
.btn-primary:hover {
background-color: #0077ed;
border-color: #0077ed;
}
.btn-outline-primary {
color: var(--apple-blue);
border-color: var(--apple-blue);
}
/* Form Controls */
.form-control, .form-select {
background-color: #fbfbfd;
border: 1px solid var(--apple-border);
border-radius: 12px;
padding: 0.75rem 1rem;
font-size: 1rem;
color: var(--apple-text);
}
.form-control:focus {
background-color: #ffffff;
border-color: var(--apple-blue);
box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.1);
} }
/* Badges */ /* Badges */
.badge { .badge {
padding: 0.5em 0.8em; border-radius: 8px;
font-weight: 500; padding: 0.4em 0.8em;
border-radius: 6px; font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.02em;
} }
/* Hero Section */ /* Stat Cards Customization */
.hero-gradient { .stat-card {
background: linear-gradient(135deg, #1e293b 0%, #334155 100%); border-radius: 20px;
color: white; }
padding: 3rem 0;
border-radius: 16px; .stat-icon {
background: rgba(0, 113, 227, 0.1);
color: var(--apple-blue);
border-radius: 14px;
}
/* Hero section */
.hero-section {
background: #ffffff;
border-radius: 24px;
padding: 4rem 2rem;
margin-bottom: 2rem; margin-bottom: 2rem;
text-align: center;
} }
/* Form Styling */ .text-secondary-apple {
.form-control, .form-select { color: var(--apple-text-secondary) !important;
border-radius: 8px;
padding: 0.625rem 0.75rem;
border: 1px solid #e2e8f0;
} }
.form-control:focus, .form-select:focus { /* Table styling */
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); .table {
border-color: #3b82f6; --bs-table-bg: transparent;
} }
.btn { .table thead th {
border-radius: 8px; font-weight: 600;
padding: 0.625rem 1.25rem; color: var(--apple-text-secondary);
font-weight: 500; text-transform: uppercase;
font-size: 0.75rem;
border-bottom: 1px solid var(--apple-border);
} }
.btn-primary { /* List group */
background-color: var(--primary); .list-group-item {
border-color: var(--primary); border-color: rgba(0, 0, 0, 0.05);
padding: 1rem 1.5rem;
} }
.btn-primary:hover { .list-group-item:first-child {
background-color: #2563eb; border-top-left-radius: 20px;
border-color: #2563eb; border-top-right-radius: 20px;
} }
/* Mobile adjustments */ .list-group-item:last-child {
@media (max-width: 768px) { border-bottom-left-radius: 20px;
.stat-card { border-bottom-right-radius: 20px;
padding: 1rem; }
}
.hero-gradient { /* Custom Scrollbar for modern look */
padding: 2rem 1rem; ::-webkit-scrollbar {
} width: 8px;
} }
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background: #d2d2d7;
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: #86868b;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 363 KiB