ver.10
This commit is contained in:
parent
9e85e053cc
commit
e5089ee366
Binary file not shown.
Binary file not shown.
Binary file not shown.
28
core/migrations/0007_maintenancepart.py
Normal file
28
core/migrations/0007_maintenancepart.py
Normal 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': 'Запчасти ТО',
|
||||
},
|
||||
),
|
||||
]
|
||||
BIN
core/migrations/__pycache__/0007_maintenancepart.cpython-311.pyc
Normal file
BIN
core/migrations/__pycache__/0007_maintenancepart.cpython-311.pyc
Normal file
Binary file not shown.
@ -216,4 +216,17 @@ class Document(models.Model):
|
||||
verbose_name_plural = "Документы"
|
||||
|
||||
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})'
|
||||
|
||||
|
||||
@ -6,11 +6,6 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<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 -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Bootstrap Icons -->
|
||||
@ -21,48 +16,45 @@
|
||||
|
||||
{% block extra_css %}{% endblock %}
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<body>
|
||||
<!-- 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">
|
||||
<a class="navbar-brand d-flex align-items-center" href="{% url 'index' %}">
|
||||
<i class="bi bi-truck-flatbed me-2 text-blue-500"></i>
|
||||
<span class="fw-bold text-uppercase tracking-wider">Fleet<span class="text-blue-500">Manager</span></span>
|
||||
<i class="bi bi-apple me-2 text-primary"></i>
|
||||
<span class="fw-bold">Fleet Manager</span>
|
||||
</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>
|
||||
</button>
|
||||
<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">
|
||||
<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 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 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 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 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>
|
||||
</ul>
|
||||
<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">
|
||||
<a href="#" class="d-block link-light text-decoration-none dropdown-toggle" 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">
|
||||
<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=0071e3&color=fff" alt="user" width="28" height="28" class="rounded-circle">
|
||||
</a>
|
||||
<ul class="dropdown-menu dropdown-menu-end text-small shadow border-0 mt-2" aria-labelledby="dropdownUser1">
|
||||
<li><a class="dropdown-item" href="/admin/">Админ-панель</a></li>
|
||||
<li><a class="dropdown-item" href="#">Профиль</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item" href="/admin/logout/">Выйти</a></li>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow-lg border-0 mt-3 rounded-4" aria-labelledby="dropdownUser1">
|
||||
<li class="px-3 py-2 small text-secondary-apple">Вошли как: <strong>{{ user.username }}</strong></li>
|
||||
<li><hr class="dropdown-divider opacity-50"></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 py-2" href="/admin/logout/"><i class="bi bi-box-arrow-right me-2"></i>Выйти</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@ -70,18 +62,23 @@
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="py-4">
|
||||
<main class="container py-5">
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer class="py-4 bg-white mt-auto border-top">
|
||||
<footer class="py-5 mt-auto">
|
||||
<div class="container text-center">
|
||||
<p class="text-muted small mb-0">© {% now "Y" %} Fleet Manager. Разработано для управления парком техники.</p>
|
||||
<p class="text-secondary-apple small mb-0">© {% now "Y" %} Fleet Manager. Совершенство в деталях.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- Bootstrap 5 JS -->
|
||||
<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 %}
|
||||
</body>
|
||||
</html>
|
||||
@ -2,56 +2,61 @@
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="fw-bold mb-0">Журнал поломок</h3>
|
||||
<a href="{% url 'breakdown_add' %}" class="btn btn-danger rounded-pill px-4">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>Сообщить о поломке
|
||||
</a>
|
||||
<div class="container">
|
||||
<div class="row mb-5">
|
||||
<div class="col-md-8">
|
||||
<h1 class="display-6 fw-bold mb-2">Журнал поломок</h1>
|
||||
<p class="text-secondary-apple">Учет неисправностей и контроль их устранения.</p>
|
||||
</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 class="card border-0 shadow-sm">
|
||||
<div class="card mb-4">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border-0 px-4">Техника</th>
|
||||
<th class="border-0">Дата</th>
|
||||
<th class="border-0">Узел / Система</th>
|
||||
<th class="border-0">Статус</th>
|
||||
<th class="border-0 text-end px-4">Действия</th>
|
||||
<th class="ps-4">Техника</th>
|
||||
<th>Дата</th>
|
||||
<th>Узел / Система</th>
|
||||
<th>Статус</th>
|
||||
<th class="pe-4 text-end">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for b in breakdowns %}
|
||||
<tr>
|
||||
<td class="px-4">
|
||||
<div class="fw-bold"><a href="{{ b.fleet_unit.get_absolute_url }}">{{ b.fleet_unit.name }}</a></div>
|
||||
<div class="small text-muted">{{ b.fleet_unit.plate_number|default:b.fleet_unit.vin }}</div>
|
||||
<td class="ps-4">
|
||||
<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-secondary-apple">{{ b.fleet_unit.plate_number|default:b.fleet_unit.vin }}</div>
|
||||
</td>
|
||||
<td>{{ b.date }}</td>
|
||||
<td>{{ b.date|date:"d.m.Y" }}</td>
|
||||
<td>{{ b.system_node }}</td>
|
||||
<td>
|
||||
{% if b.status == 'reported' %}
|
||||
<span class="badge bg-danger rounded-pill">Заявлено</span>
|
||||
<span class="badge bg-danger">Заявлено</span>
|
||||
{% elif b.status == 'repaired' %}
|
||||
<span class="badge bg-success rounded-pill">Отремонтировано</span>
|
||||
<span class="badge bg-success">Отремонтировано</span>
|
||||
{% elif b.status == 'need_part' %}
|
||||
<span class="badge bg-warning text-dark rounded-pill">Нужна деталь</span>
|
||||
<span class="badge bg-warning text-dark">Нужна деталь</span>
|
||||
{% else %}
|
||||
<span class="badge bg-secondary rounded-pill">{{ b.status }}</span>
|
||||
<span class="badge bg-secondary">{{ b.get_status_display }}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="text-end px-4">
|
||||
<div class="btn-group">
|
||||
<a href="{{ b.fleet_unit.get_absolute_url }}" class="btn btn-sm btn-outline-primary rounded-pill px-3 me-2">К технике</a>
|
||||
<td class="pe-4 text-end">
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
<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' %}
|
||||
<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 %}
|
||||
{% if user.is_staff %}
|
||||
<form action="{% url 'breakdown_delete' b.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись о поломке?')">
|
||||
{% 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>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -59,7 +64,7 @@
|
||||
</tr>
|
||||
{% empty %}
|
||||
<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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@ -67,4 +72,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -2,47 +2,56 @@
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row">
|
||||
<div class="container">
|
||||
<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 -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card mb-4 overflow-hidden">
|
||||
{% 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 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
<div class="card-body">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<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="card-body p-4">
|
||||
<h5 class="fw-bold mb-4">Характеристики</h5>
|
||||
<div class="row g-3 mb-4">
|
||||
<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>
|
||||
</div>
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<p class="text-muted small mb-0">VIN / Серийный номер</p>
|
||||
<div class="col-12 border-top pt-3">
|
||||
<p class="text-secondary-apple small mb-0">VIN / Серийный номер</p>
|
||||
<p class="fw-bold mb-0">{{ unit.vin }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if unit.insurance_company %}
|
||||
<div class="bg-light p-3 rounded mb-4">
|
||||
<h6 class="fw-bold small mb-2"><i class="bi bi-shield-check me-2"></i>Страховка</h6>
|
||||
<div class="p-3 rounded-4 bg-light mb-4">
|
||||
<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_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>
|
||||
@ -50,89 +59,89 @@
|
||||
{% endif %}
|
||||
|
||||
<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 %}
|
||||
<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 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
<a href="{% url 'breakdown_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-danger rounded-pill">Заявить о поломке</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card shadow-sm border-0 text-center p-4">
|
||||
<h6 class="fw-bold mb-3">QR-код техники</h6>
|
||||
<div class="card p-4 text-center">
|
||||
<h6 class="fw-bold mb-3">QR-код доступа</h6>
|
||||
{% 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 %}
|
||||
<div class="alert alert-light small">QR-код генерируется...</div>
|
||||
<div class="text-secondary-apple small py-3">Генерация...</div>
|
||||
{% endif %}
|
||||
<p class="text-muted small mb-0">Наклейте этот код на технику для быстрого доступа механика.</p>
|
||||
<button onclick="window.print()" class="btn btn-sm btn-link mt-2">Печать кода</button>
|
||||
<p class="text-secondary-apple small mb-3">Для быстрого перехода механика к истории и заявкам.</p>
|
||||
<button onclick="window.print()" class="btn btn-sm btn-link text-decoration-none">Печать кода <i class="bi bi-printer ms-1"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main Content: Tabs -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white border-0 pt-3">
|
||||
<ul class="nav nav-pills" id="fleetTab" role="tablist">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<ul class="nav nav-tabs border-0" id="fleetTab" role="tablist">
|
||||
<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 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 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 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>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="card-body p-4">
|
||||
<div class="tab-content" id="fleetTabContent">
|
||||
<!-- Maintenance Tab -->
|
||||
<div class="tab-pane fade show active" id="history">
|
||||
<div class="d-flex justify-content-between align-items-center mb-3">
|
||||
<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>
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h6 class="fw-bold mb-0">История и планирование</h6>
|
||||
<a href="{% url 'maintenance_add' %}?fleet_unit={{ unit.pk }}" class="btn btn-sm btn-primary">+ Запланировать</a>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<table class="table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Тип</th>
|
||||
<th>Дата</th>
|
||||
<th>Статус</th>
|
||||
<th>Исполнитель</th>
|
||||
<th></th>
|
||||
<th>Механик</th>
|
||||
<th class="text-end">Действие</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for m in maintenances %}
|
||||
<tr>
|
||||
<td>{{ m.m_type }}</td>
|
||||
<td class="fw-bold">{{ m.m_type }}</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>{{ m.mechanic|default:"-" }}</td>
|
||||
<td><span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }}">{{ m.get_status_display }}</span></td>
|
||||
<td class="small">{{ m.mechanic|default:"-" }}</td>
|
||||
<td class="text-end">
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-link text-primary p-0 me-2">Открыть</a>
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
<a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-link text-decoration-none">Открыть</a>
|
||||
{% 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 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% 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 %}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -141,72 +150,72 @@
|
||||
|
||||
<!-- Breakdowns Tab -->
|
||||
<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>
|
||||
<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 class="list-group list-group-flush">
|
||||
<div class="list-group list-group-flush border-top">
|
||||
{% for b in breakdowns %}
|
||||
<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>
|
||||
<div>
|
||||
<span class="badge bg-light text-dark border small me-2">{{ b.get_status_display }}</span>
|
||||
<div class="d-flex align-items-center">
|
||||
<span class="badge bg-light text-dark border-0 shadow-sm me-3">{{ b.get_status_display }}</span>
|
||||
{% 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 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<p class="small mb-1 text-muted">{{ b.date|date:"d.m.Y" }} — {{ b.description }}</p>
|
||||
<div class="d-flex align-items-center mt-2">
|
||||
<a href="{% url 'part_request_add' %}?breakdown={{ b.pk }}" class="btn btn-sm btn-outline-primary rounded-pill py-0 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 %}
|
||||
<p class="small text-secondary-apple mb-3">{{ b.date|date:"d.m.Y" }} — {{ b.description }}</p>
|
||||
<div class="d-flex align-items-center">
|
||||
<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="text-secondary-apple small"><i class="bi bi-image me-1"></i>Фото приложено</span>{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="text-center py-4 text-muted">Поломок не зафиксировано</div>
|
||||
<div class="text-center py-5 text-secondary-apple">Журнал пуст</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Parts Tab -->
|
||||
<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>
|
||||
<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 class="table-responsive">
|
||||
<table class="table table-sm align-middle">
|
||||
<table class="table align-middle">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Деталь</th>
|
||||
<th>Кол-во</th>
|
||||
<th>Статус</th>
|
||||
<th>Дата</th>
|
||||
<th></th>
|
||||
<th class="text-end"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in part_requests %}
|
||||
<tr>
|
||||
<td>{{ r.part_name }}</td>
|
||||
<td class="fw-bold">{{ r.part_name }}</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="text-end">
|
||||
{% 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 %}
|
||||
<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>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% 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 %}
|
||||
</tbody>
|
||||
</table>
|
||||
@ -215,85 +224,63 @@
|
||||
|
||||
<!-- Supply Tab -->
|
||||
<div class="tab-pane fade" id="supply">
|
||||
<h6 class="fw-bold mb-3">Данные по снабжению и поставщику</h6>
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="mb-3">
|
||||
<p class="text-muted small mb-1">Номер договора (техника)</p>
|
||||
<p class="fw-bold mb-0">{{ unit.contract_number|default:"-" }}</p>
|
||||
<div class="col-md-7">
|
||||
<div class="mb-4">
|
||||
<h6 class="fw-bold mb-3">Договорные данные</h6>
|
||||
<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>
|
||||
|
||||
{% if unit.supplier %}
|
||||
<p class="text-muted small mb-1">Поставщик / Контрагент</p>
|
||||
<p class="fw-bold mb-1">{{ unit.supplier.name }}</p>
|
||||
|
||||
<p class="text-muted small mb-1">Представитель</p>
|
||||
<p class="small mb-1">{{ unit.supplier.representative_name|default:"-" }}</p>
|
||||
<p class="small mb-1"><i class="bi bi-telephone me-2"></i>{{ unit.supplier.phone|default:"-" }}</p>
|
||||
<p class="small"><i class="bi bi-envelope me-2"></i>{{ unit.supplier.email|default:"-" }}</p>
|
||||
{% else %}
|
||||
<p class="text-muted small">Поставщик не привязан</p>
|
||||
<p class="small text-muted">Вы можете выбрать поставщика в режиме редактирования техники.</p>
|
||||
{% endif %}
|
||||
<div>
|
||||
<h6 class="fw-bold mb-3">Поставщик</h6>
|
||||
{% if unit.supplier %}
|
||||
<div class="card p-3 border-0 shadow-sm bg-white">
|
||||
<p class="fw-bold mb-1">{{ unit.supplier.name }}</p>
|
||||
<p class="text-secondary-apple small mb-2">{{ unit.supplier.representative_name|default:"Контактное лицо не указано" }}</p>
|
||||
<div class="d-flex flex-column gap-1">
|
||||
<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>
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-light border-0 small">Поставщик не привязан к данной технике.</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 text-end">
|
||||
<p class="text-muted small mb-1">Документы на авто</p>
|
||||
<div class="col-md-5">
|
||||
<h6 class="fw-bold mb-3">Документация</h6>
|
||||
{% if unit.vehicle_documents %}
|
||||
<a href="{{ unit.vehicle_documents.url }}" class="btn btn-outline-secondary btn-sm rounded-pill" target="_blank">
|
||||
<i class="bi bi-file-earmark-text me-2"></i>Открыть документы
|
||||
<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-pdf text-danger display-4 mb-2"></i>
|
||||
<div class="fw-bold">Посмотреть ПТС/СТС</div>
|
||||
<div class="small text-secondary-apple">Загруженный файл</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<p class="text-muted small">Не загружены</p>
|
||||
<div class="text-center p-5 bg-light rounded-4 text-secondary-apple small">
|
||||
Документы не загружены
|
||||
</div>
|
||||
{% endif %}
|
||||
</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 class="card shadow-sm border-0 mt-4">
|
||||
<div class="card-header bg-white fw-bold">Примечания</div>
|
||||
<div class="card-body">
|
||||
<p class="mb-0">{{ unit.notes|default:"Нет примечаний" }}</p>
|
||||
</div>
|
||||
<div class="card mt-4 p-4">
|
||||
<h6 class="fw-bold mb-2">Заметки и примечания</h6>
|
||||
<p class="text-secondary-apple mb-0">{{ unit.notes|default:"Дополнительная информация отсутствует." }}</p>
|
||||
</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 %}
|
||||
@ -1,122 +1,128 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container py-5">
|
||||
<div class="container">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-10">
|
||||
<div class="card shadow-sm border-0">
|
||||
<div class="card-header bg-white border-0 py-4">
|
||||
<h2 class="h4 mb-0 text-center">
|
||||
{% if object %}Редактировать технику{% else %}Добавить новую технику{% endif %}
|
||||
</h2>
|
||||
<div class="card p-4 p-md-5">
|
||||
<div class="text-center mb-5">
|
||||
<h1 class="fw-bold mb-2">
|
||||
{% if object %}Редактировать технику{% else %}Новая единица техники{% endif %}
|
||||
</h1>
|
||||
<p class="text-secondary-apple">Заполните данные для корректного учета и планирования ТО.</p>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Section: Basic Info -->
|
||||
<h5 class="mb-3 text-primary border-bottom pb-2">Основная информация</h5>
|
||||
<div class="row g-3 mb-4">
|
||||
|
||||
<form method="post" enctype="multipart/form-data">
|
||||
{% csrf_token %}
|
||||
|
||||
<!-- Section: Basic Info -->
|
||||
<div class="mb-5">
|
||||
<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">
|
||||
<label class="form-label">Наименование *</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Наименование *</label>
|
||||
{{ form.name }}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Модель *</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Модель *</label>
|
||||
{{ form.model_name }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Категория</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Категория</label>
|
||||
{{ form.category }}
|
||||
</div>
|
||||
<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 }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Госномер</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Госномер</label>
|
||||
{{ form.plate_number }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Год выпуска *</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Год выпуска *</label>
|
||||
{{ form.year }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Дата ввода в эксплуатацию *</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Дата ввода в эксплуатацию *</label>
|
||||
{{ form.commissioning_date }}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label">Статус</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Статус</label>
|
||||
{{ form.status }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section: Insurance -->
|
||||
<h5 class="mb-3 text-primary border-bottom pb-2">Страховка</h5>
|
||||
<div class="row g-3 mb-4">
|
||||
<!-- Section: Insurance -->
|
||||
<div class="mb-5">
|
||||
<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">
|
||||
<label class="form-label">Страховая компания</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Страховая компания</label>
|
||||
{{ form.insurance_company }}
|
||||
</div>
|
||||
<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 }}
|
||||
</div>
|
||||
<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 }}
|
||||
</div>
|
||||
<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 }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section: Supply -->
|
||||
<h5 class="mb-3 text-primary border-bottom pb-2">Снабжение</h5>
|
||||
<div class="row g-3 mb-4">
|
||||
<!-- Section: Supply -->
|
||||
<div class="mb-5">
|
||||
<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">
|
||||
<label class="form-label d-flex justify-content-between">
|
||||
Поставщик / Контрагент
|
||||
<a href="{% url 'supplier_create' %}" target="_blank" class="small text-decoration-none">
|
||||
<i class="bi bi-plus-circle"></i> Создать нового
|
||||
<label class="form-label d-flex justify-content-between small fw-bold text-secondary-apple">
|
||||
Поставщик
|
||||
<a href="{% url 'supplier_create' %}" target="_blank" class="fw-normal text-decoration-none">
|
||||
+ Добавить в базу
|
||||
</a>
|
||||
</label>
|
||||
{{ form.supplier }}
|
||||
<div class="form-text">Выберите поставщика из списка или создайте нового (откроется в новой вкладке).</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Номер договора</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Номер договора</label>
|
||||
{{ form.contract_number }}
|
||||
<div class="form-text">Номер договора по данной единице техники.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Документы на авто (PDF/Image)</label>
|
||||
<div class="col-md-12">
|
||||
<label class="form-label small fw-bold text-secondary-apple">Документы на авто (PDF/Image)</label>
|
||||
{{ form.vehicle_documents }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Section: Media & Notes -->
|
||||
<h5 class="mb-3 text-primary border-bottom pb-2">Медиа и примечания</h5>
|
||||
<div class="row g-3 mb-4">
|
||||
<!-- Section: Media & Notes -->
|
||||
<div class="mb-5">
|
||||
<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">
|
||||
<label class="form-label">Фото техники</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Фотографии техники</label>
|
||||
{{ form.photo }}
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="form-label">Примечания</label>
|
||||
<label class="form-label small fw-bold text-secondary-apple">Примечания</label>
|
||||
{{ form.notes }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="d-grid gap-2 d-md-flex justify-content-md-end mt-5">
|
||||
<a href="{% if object %}{% url 'fleet_detail' object.pk %}{% else %}{% url 'fleet_list' %}{% endif %}" class="btn btn-light px-4">Отмена</a>
|
||||
<button type="submit" class="btn btn-primary px-5">Сохранить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<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-link text-decoration-none text-secondary-apple">Отмена</a>
|
||||
<button type="submit" class="btn btn-primary px-5">Сохранить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -3,95 +3,81 @@
|
||||
{% block title %}Список техники | Fleet Manager{% endblock %}
|
||||
|
||||
{% 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>
|
||||
<h2 class="mb-1">Парк техники</h2>
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb mb-0">
|
||||
<li class="breadcrumb-item"><a href="{% url 'index' %}" class="text-decoration-none">Дашборд</a></li>
|
||||
<li class="breadcrumb-item active" aria-current="page">Техника</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div class="row mb-5">
|
||||
<div class="col-md-8">
|
||||
<h1 class="display-6 fw-bold mb-2">Парк техники</h1>
|
||||
<p class="text-secondary-apple">Управление и мониторинг каждой единицы оборудования.</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-md-end d-flex align-items-center justify-content-md-end">
|
||||
<a href="{% url 'fleet_add' %}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-lg me-2"></i>Добавить технику
|
||||
</a>
|
||||
</div>
|
||||
<a href="{% url 'fleet_add' %}" class="btn btn-primary">
|
||||
<i class="bi bi-plus-lg me-2"></i>Добавить технику
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Filters -->
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<form method="get" class="row g-3">
|
||||
<div class="col-md-5">
|
||||
<div class="input-group">
|
||||
<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" placeholder="Поиск по названию или номеру..." value="{{ request.GET.search }}">
|
||||
</div>
|
||||
<div class="card p-4 mb-5">
|
||||
<form method="get" class="row g-3 align-items-center">
|
||||
<div class="col-md-5">
|
||||
<div class="input-group">
|
||||
<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-start-0 ps-0" placeholder="Поиск по названию или номеру..." value="{{ request.GET.search }}">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select name="status" class="form-select" onchange="this.form.submit()">
|
||||
<option value="">Все статусы</option>
|
||||
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>В работе</option>
|
||||
<option value="idle" {% if request.GET.status == 'idle' %}selected{% endif %}>Простаивает</option>
|
||||
<option value="broken" {% if request.GET.status == 'broken' %}selected{% endif %}>Сломана</option>
|
||||
<option value="repair" {% if request.GET.status == 'repair' %}selected{% endif %}>В ремонте</option>
|
||||
<option value="waiting_parts" {% if request.GET.status == 'waiting_parts' %}selected{% endif %}>Ждёт деталь</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button type="submit" class="btn btn-outline-secondary w-100">Применить</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<select name="status" class="form-select" onchange="this.form.submit()">
|
||||
<option value="">Все статусы</option>
|
||||
<option value="active" {% if request.GET.status == 'active' %}selected{% endif %}>В работе</option>
|
||||
<option value="idle" {% if request.GET.status == 'idle' %}selected{% endif %}>Простаивает</option>
|
||||
<option value="broken" {% if request.GET.status == 'broken' %}selected{% endif %}>Сломана</option>
|
||||
<option value="repair" {% if request.GET.status == 'repair' %}selected{% endif %}>В ремонте</option>
|
||||
<option value="waiting_parts" {% if request.GET.status == 'waiting_parts' %}selected{% endif %}>Ждёт деталь</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button type="submit" class="btn btn-outline-primary w-100">Фильтровать</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
{% for unit in units %}
|
||||
<div class="col-12 col-md-6 col-xl-4">
|
||||
<div class="card h-100 shadow-sm border-0">
|
||||
<div class="row g-0 h-100">
|
||||
<div class="col-4">
|
||||
{% if unit.photo %}
|
||||
<img src="{{ unit.photo.url }}" class="img-fluid rounded-start h-100 object-fit-cover" alt="{{ unit.name }}" style="min-height: 160px;">
|
||||
{% else %}
|
||||
<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 class="col-12 col-md-6 col-lg-4">
|
||||
<div class="card h-100">
|
||||
{% if unit.photo %}
|
||||
<img src="{{ unit.photo.url }}" class="card-img-top object-fit-cover" alt="{{ unit.name }}" style="height: 200px;">
|
||||
{% else %}
|
||||
<div class="bg-light d-flex align-items-center justify-content-center" style="height: 200px;">
|
||||
<i class="bi bi-truck text-secondary-apple display-4"></i>
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<div class="card-body d-flex flex-column h-100">
|
||||
<div class="d-flex justify-content-between align-items-start mb-2">
|
||||
<span class="badge bg-{{ unit.get_status_color }} bg-opacity-10 text-{{ unit.get_status_color }} border border-{{ unit.get_status_color }}">
|
||||
{{ unit.get_status_display }}
|
||||
</span>
|
||||
<span class="text-muted small">#{{ unit.pk }}</span>
|
||||
</div>
|
||||
<h5 class="card-title mb-1">
|
||||
<a href="{{ unit.get_absolute_url }}" class="text-decoration-none text-slate-900">{{ unit.name }}</a>
|
||||
</h5>
|
||||
<p class="card-text text-muted small mb-3">
|
||||
{{ unit.model_name }} | {{ unit.plate_number|default:unit.vin }}
|
||||
</p>
|
||||
<div class="mt-auto d-flex justify-content-between align-items-center">
|
||||
<div class="small text-muted">
|
||||
<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>
|
||||
{% endif %}
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex justify-content-between align-items-start mb-3">
|
||||
<span class="badge bg-{{ unit.get_status_color }} text-white">
|
||||
{{ unit.get_status_display }}
|
||||
</span>
|
||||
<span class="text-secondary-apple small">#{{ unit.pk }}</span>
|
||||
</div>
|
||||
<h5 class="fw-bold mb-1">
|
||||
<a href="{{ unit.get_absolute_url }}" class="text-decoration-none text-dark">{{ unit.name }}</a>
|
||||
</h5>
|
||||
<p class="text-secondary-apple small mb-4">
|
||||
{{ unit.model_name }} | {{ unit.plate_number|default:unit.vin }}
|
||||
</p>
|
||||
<div class="d-flex justify-content-between align-items-center pt-3 border-top">
|
||||
<div class="small text-secondary-apple">
|
||||
<i class="bi bi-calendar3 me-1"></i>{{ unit.year }}
|
||||
</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>
|
||||
{% empty %}
|
||||
<div class="col-12 text-center py-5">
|
||||
<div class="mb-3">
|
||||
<i class="bi bi-inbox text-muted display-1"></i>
|
||||
</div>
|
||||
<h4 class="text-muted">Техника не найдена</h4>
|
||||
<p class="text-muted">Попробуйте изменить параметры поиска или добавить новую единицу.</p>
|
||||
<a href="{% url 'fleet_add' %}" class="btn btn-primary mt-2">Добавить технику</a>
|
||||
<i class="bi bi-inbox text-secondary-apple display-1 mb-3"></i>
|
||||
<h4 class="fw-bold">Техника не найдена</h4>
|
||||
<p class="text-secondary-apple">Попробуйте изменить параметры поиска.</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
@ -99,21 +85,19 @@
|
||||
<!-- Pagination -->
|
||||
{% if is_paginated %}
|
||||
<nav class="mt-5">
|
||||
<ul class="pagination justify-content-center">
|
||||
<ul class="pagination justify-content-center border-0">
|
||||
{% 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 %}
|
||||
|
||||
{% for i in paginator.page_range %}
|
||||
<li class="page-item {% if page_obj.number == i %}active{% endif %}">
|
||||
<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>
|
||||
<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>
|
||||
{% endfor %}
|
||||
|
||||
{% 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 %}
|
||||
</ul>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -2,59 +2,50 @@
|
||||
{% load static %}
|
||||
|
||||
{% 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 -->
|
||||
<div class="row g-4 mb-4">
|
||||
<div class="row g-4 mb-5">
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-sm bg-slate-900 text-white p-3 h-100">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-blue-500 p-3 me-3">
|
||||
<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 class="card p-4 h-100 text-center">
|
||||
<div class="stat-icon mx-auto mb-3 d-flex align-items-center justify-content-center" style="width: 56px; height: 56px;">
|
||||
<i class="bi bi-truck fs-3"></i>
|
||||
</div>
|
||||
<h3 class="fw-bold mb-1">{{ total_units }}</h3>
|
||||
<span class="text-secondary-apple small">Всего техники</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-sm p-3 h-100">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-success-subtle p-3 me-3 text-success">
|
||||
<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 class="card p-4 h-100 text-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;">
|
||||
<i class="bi bi-check-circle fs-3"></i>
|
||||
</div>
|
||||
<h3 class="fw-bold mb-1 text-success">{{ active_units }}</h3>
|
||||
<span class="text-secondary-apple small">В работе</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-sm p-3 h-100">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-danger-subtle p-3 me-3 text-danger">
|
||||
<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 class="card p-4 h-100 text-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;">
|
||||
<i class="bi bi-exclamation-triangle fs-3"></i>
|
||||
</div>
|
||||
<h3 class="fw-bold mb-1 text-danger">{{ broken_units }}</h3>
|
||||
<span class="text-secondary-apple small">Сломано</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card border-0 shadow-sm p-3 h-100">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="rounded-circle bg-warning-subtle p-3 me-3 text-warning">
|
||||
<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 class="card p-4 h-100 text-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;">
|
||||
<i class="bi bi-wrench fs-3"></i>
|
||||
</div>
|
||||
<h3 class="fw-bold mb-1 text-warning">{{ repair_units }}</h3>
|
||||
<span class="text-secondary-apple small">В ремонте</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -62,53 +53,53 @@
|
||||
<div class="row g-4">
|
||||
<!-- Recent Activities -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<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 class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Техника</th>
|
||||
<th class="ps-4">Техника</th>
|
||||
<th>Тип</th>
|
||||
<th>Дата</th>
|
||||
<th>Статус</th>
|
||||
<th class="pe-4 text-end">Статус</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for m in recent_maintenances %}
|
||||
<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.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>
|
||||
{% 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 %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<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 class="list-group list-group-flush">
|
||||
{% 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 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>
|
||||
<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>
|
||||
{% 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 %}
|
||||
</div>
|
||||
</div>
|
||||
@ -116,45 +107,39 @@
|
||||
|
||||
<!-- Quick Actions & Search -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body">
|
||||
<h6 class="fw-bold mb-3">Быстрый поиск</h6>
|
||||
<form action="{% url 'fleet_list' %}" method="get">
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" name="search" class="form-control border-end-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 class="card mb-4 p-4">
|
||||
<h6 class="fw-bold mb-3">Поиск</h6>
|
||||
<form action="{% url 'fleet_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<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-start-0 ps-0" placeholder="Госномер или VIN...">
|
||||
</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 class="card border-0 shadow-sm bg-blue-50">
|
||||
<div class="card-body">
|
||||
<h6 class="fw-bold text-blue-900 mb-2">Мобильное приложение</h6>
|
||||
<p class="small text-blue-800 mb-3">Приложение оптимизировано для работы механиков с телефона. Используйте QR-коды для быстрого доступа к чек-листам.</p>
|
||||
<i class="bi bi-phone-vibrate text-blue-500 display-4 d-block text-center"></i>
|
||||
<div class="card p-4 border-0" style="background: linear-gradient(135deg, #eef2f3 0%, #8e9eab 100%);">
|
||||
<h6 class="fw-bold mb-2">Мобильная версия</h6>
|
||||
<p class="small text-secondary-apple mb-3">Используйте камеру телефона для сканирования QR-кодов на технике для быстрого доступа.</p>
|
||||
<div class="text-center">
|
||||
<i class="bi bi-phone fs-1"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -2,132 +2,200 @@
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="row">
|
||||
<div class="container">
|
||||
<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="card shadow-sm mb-4 border-0">
|
||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0 fw-bold">
|
||||
<i class="bi bi-tools text-primary me-2"></i>
|
||||
{{ maintenance.m_type }} - {{ maintenance.fleet_unit.name }}
|
||||
</h5>
|
||||
<span class="badge bg-{{ maintenance.status|yesno:'success,warning,secondary' }} rounded-pill px-3">
|
||||
{% if maintenance.status == 'planned' %}Планируется
|
||||
{% elif maintenance.status == 'in_progress' %}В процессе
|
||||
{% else %}Выполнено{% endif %}
|
||||
</span>
|
||||
<div class="card p-4 mb-4">
|
||||
<h5 class="fw-bold mb-4">Детали обслуживания</h5>
|
||||
<div class="row mb-4">
|
||||
<div class="col-md-6">
|
||||
<p class="text-secondary-apple small mb-1">Техника</p>
|
||||
<p class="fw-bold mb-3"><a href="{{ maintenance.fleet_unit.get_absolute_url }}" class="text-dark">{{ maintenance.fleet_unit }}</a></p>
|
||||
|
||||
<p class="text-secondary-apple small mb-1">Плановая дата</p>
|
||||
<p class="fw-bold mb-3">{{ maintenance.planned_date }}</p>
|
||||
</div>
|
||||
<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 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>
|
||||
<div class="list-group list-group-flush border rounded mb-4">
|
||||
{% for item in maintenance.checklist %}
|
||||
<div class="list-group-item d-flex align-items-center py-3">
|
||||
<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">
|
||||
<h5 class="fw-bold mb-4">Чек-лист операций</h5>
|
||||
<div class="list-group list-group-flush mb-5">
|
||||
{% for item in maintenance.checklist %}
|
||||
<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">
|
||||
{% csrf_token %}
|
||||
<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">Фактическая дата</label>
|
||||
<input type="date" name="actual_date" class="form-control" value="{% now 'Y-m-d' %}" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label small fw-bold">Фактический пробег/моточасы</label>
|
||||
<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="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>
|
||||
<input type="hidden" name="task_index" value="{{ forloop.counter0 }}">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input border-secondary-apple" 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-secondary-apple{% endif %}" for="task_{{ forloop.counter0 }}">
|
||||
{{ item.task }}
|
||||
</label>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="mt-4 pt-3 border-top">
|
||||
<div class="alert alert-success d-flex align-items-center">
|
||||
<i class="bi bi-info-circle-fill me-2"></i>
|
||||
<div>ТО успешно завершено {{ maintenance.actual_date }}</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% empty %}
|
||||
<div class="text-center py-4 text-secondary-apple">Чек-лист пуст</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<h5 class="fw-bold mb-4">Расходные материалы</h5>
|
||||
<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">
|
||||
<p class="text-muted small mb-1">Фактический пробег/моточасы</p>
|
||||
<p class="fw-bold">{{ maintenance.actual_runtime }}</p>
|
||||
<label class="form-label small fw-bold">Фактическая дата</label>
|
||||
<input type="date" name="actual_date" class="form-control" value="{% now 'Y-m-d' %}" required>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<p class="text-muted small mb-1">Запчасти</p>
|
||||
<p>{{ maintenance.parts_used|default:"-" }}</p>
|
||||
<label class="form-label small fw-bold">Фактический пробег</label>
|
||||
<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>
|
||||
<a href="{% url 'maintenance_pdf' maintenance.pk %}" class="btn btn-outline-primary btn-sm mt-2">
|
||||
<i class="bi bi-file-pdf me-1"></i>Скачать Акт ТО (PDF)
|
||||
</a>
|
||||
</form>
|
||||
</div>
|
||||
{% 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>
|
||||
{% 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>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm border-0 mb-4">
|
||||
<div class="card-header bg-white fw-bold py-3">Примечания</div>
|
||||
<div class="card-body">
|
||||
<p class="mb-0">{{ maintenance.notes|default:"Нет примечаний" }}</p>
|
||||
<div class="card p-4 mb-4">
|
||||
<h6 class="fw-bold mb-3">Техника</h6>
|
||||
<div class="d-flex align-items-center mb-4">
|
||||
{% 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>
|
||||
|
||||
{% if maintenance.fleet_unit.photo %}
|
||||
<div class="card shadow-sm border-0">
|
||||
<img src="{{ maintenance.fleet_unit.photo.url }}" class="card-img-top" alt="{{ maintenance.fleet_unit.name }}">
|
||||
<div class="card-body">
|
||||
<p class="card-text small text-muted text-center">{{ maintenance.fleet_unit.name }}</p>
|
||||
</div>
|
||||
|
||||
<div class="card p-4">
|
||||
<h6 class="fw-bold mb-3">Исходные примечания</h6>
|
||||
<p class="text-secondary-apple small mb-0">{{ maintenance.notes|default:"Примечания отсутствуют" }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -2,51 +2,54 @@
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="fw-bold mb-0">Техническое обслуживание</h3>
|
||||
<a href="{% url 'maintenance_add' %}" class="btn btn-primary rounded-pill px-4">
|
||||
<i class="bi bi-plus-lg me-2"></i>Запланировать ТО
|
||||
</a>
|
||||
<div class="container">
|
||||
<div class="row mb-5">
|
||||
<div class="col-md-8">
|
||||
<h1 class="display-6 fw-bold mb-2">Техническое обслуживание</h1>
|
||||
<p class="text-secondary-apple">Планирование и контроль регламентных работ.</p>
|
||||
</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 class="card border-0 shadow-sm">
|
||||
<div class="card mb-4">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border-0 px-4">Техника</th>
|
||||
<th class="border-0">Тип</th>
|
||||
<th class="border-0">Плановая дата</th>
|
||||
<th class="border-0">Статус</th>
|
||||
<th class="border-0">Исполнитель</th>
|
||||
<th class="border-0 text-end px-4">Действия</th>
|
||||
<th class="ps-4">Техника</th>
|
||||
<th>Тип</th>
|
||||
<th>Дата</th>
|
||||
<th>Статус</th>
|
||||
<th>Механик</th>
|
||||
<th class="pe-4 text-end">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for m in maintenances %}
|
||||
<tr>
|
||||
<td class="px-4">
|
||||
<div class="fw-bold"><a href="{{ m.fleet_unit.get_absolute_url }}">{{ m.fleet_unit.name }}</a></div>
|
||||
<div class="small text-muted">{{ m.fleet_unit.plate_number|default:m.fleet_unit.vin }}</div>
|
||||
<td class="ps-4">
|
||||
<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-secondary-apple">{{ m.fleet_unit.plate_number|default:m.fleet_unit.vin }}</div>
|
||||
</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>
|
||||
<span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }} rounded-pill">
|
||||
{% if m.status == 'planned' %}Планируется
|
||||
{% elif m.status == 'in_progress' %}В процессе
|
||||
{% else %}Выполнено{% endif %}
|
||||
<span class="badge bg-{{ m.status|yesno:'success,warning,secondary' }}">
|
||||
{{ m.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ m.mechanic|default:"-" }}</td>
|
||||
<td class="text-end px-4">
|
||||
<div class="btn-group">
|
||||
<a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-outline-primary rounded-pill px-3 me-2">Открыть</a>
|
||||
<td class="small">{{ m.mechanic|default:"-" }}</td>
|
||||
<td class="pe-4 text-end">
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
<a href="{% url 'maintenance_detail' m.pk %}" class="btn btn-sm btn-link text-decoration-none me-2">Открыть</a>
|
||||
{% if user.is_staff %}
|
||||
<form action="{% url 'maintenance_delete' m.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить запись ТО?')">
|
||||
{% 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>
|
||||
{% endif %}
|
||||
</div>
|
||||
@ -54,7 +57,7 @@
|
||||
</tr>
|
||||
{% empty %}
|
||||
<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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@ -62,4 +65,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
@ -2,37 +2,42 @@
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h3 class="fw-bold mb-0">Заявки на запчасти</h3>
|
||||
<a href="{% url 'part_request_add' %}" class="btn btn-primary rounded-pill px-4">
|
||||
<i class="bi bi-plus-lg me-2"></i>Новая заявка
|
||||
</a>
|
||||
<div class="container">
|
||||
<div class="row mb-5">
|
||||
<div class="col-md-8">
|
||||
<h1 class="display-6 fw-bold mb-2">Заявки на запчасти</h1>
|
||||
<p class="text-secondary-apple">Отслеживание поставок и управление запасами.</p>
|
||||
</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 class="card border-0 shadow-sm">
|
||||
<div class="card mb-4">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead class="bg-light">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border-0 px-4">Техника</th>
|
||||
<th class="border-0">Деталь</th>
|
||||
<th class="border-0">Артикул</th>
|
||||
<th class="border-0">Кол-во</th>
|
||||
<th class="border-0">Статус</th>
|
||||
<th class="border-0">Дата создания</th>
|
||||
<th class="border-0 text-end px-4">Действия</th>
|
||||
<th class="ps-4">Техника</th>
|
||||
<th>Деталь</th>
|
||||
<th>Артикул</th>
|
||||
<th>Кол-во</th>
|
||||
<th>Статус</th>
|
||||
<th>Дата</th>
|
||||
<th class="pe-4 text-end">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for r in requests %}
|
||||
<tr>
|
||||
<td class="px-4">
|
||||
<td class="ps-4">
|
||||
<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>{{ r.part_name }}</td>
|
||||
<td>{{ r.article_number|default:"-" }}</td>
|
||||
<td class="fw-bold text-dark">{{ r.part_name }}</td>
|
||||
<td class="small">{{ r.article_number|default:"-" }}</td>
|
||||
<td>{{ r.quantity }}</td>
|
||||
<td>
|
||||
{% if r.status == 'draft' %}<span class="badge bg-secondary">Черновик</span>
|
||||
@ -41,24 +46,24 @@
|
||||
{% elif r.status == 'delivered' %}<span class="badge bg-success">Доставлено</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="small">{{ r.created_at|date:"d.m.Y H:i" }}</td>
|
||||
<td class="text-end px-4">
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary dropdown-toggle rounded-pill px-3" data-bs-toggle="dropdown">
|
||||
Действие
|
||||
<td class="small text-secondary-apple">{{ r.created_at|date:"d.m.Y" }}</td>
|
||||
<td class="pe-4 text-end">
|
||||
<div class="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>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow-sm border-0">
|
||||
<li><a class="dropdown-item" href="#">Изменить статус</a></li>
|
||||
<li><a class="dropdown-item" href="#">Экспорт Excel</a></li>
|
||||
<li><hr class="dropdown-divider"></li>
|
||||
<li><a class="dropdown-item text-danger" href="#">Удалить</a></li>
|
||||
<ul class="dropdown-menu dropdown-menu-end shadow-lg border-0 rounded-3">
|
||||
<li><a class="dropdown-item py-2" href="#"><i class="bi bi-pencil me-2"></i>Изменить</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 opacity-50"></li>
|
||||
<li><a class="dropdown-item py-2 text-danger" href="#"><i class="bi bi-trash me-2"></i>Удалить</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<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>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
@ -66,4 +71,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -1,116 +1,112 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid py-4">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h1 class="h2">Снабжение</h1>
|
||||
<div class="container">
|
||||
<div class="row mb-5">
|
||||
<div class="col-12 text-center">
|
||||
<h1 class="display-5 fw-bold mb-2">Снабжение</h1>
|
||||
<p class="text-secondary-apple">Управление закупками и работа с контрагентами.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="row g-4">
|
||||
<!-- Part Requests Section -->
|
||||
<div class="col-lg-8">
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h5 class="card-title mb-0">Заявки на запчасти</h5>
|
||||
<div class="card mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="fw-bold mb-0">Заявки на запчасти</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle">
|
||||
<thead class="table-light">
|
||||
<tr>
|
||||
<th>Дата</th>
|
||||
<th>Техника</th>
|
||||
<th>Деталь</th>
|
||||
<th>Кол-во</th>
|
||||
<th>Статус</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for req in requests %}
|
||||
<tr>
|
||||
<td>{{ req.created_at|date:"d.m.Y" }}</td>
|
||||
<td>
|
||||
<a href="{% url 'fleet_detail' req.fleet_unit.pk %}" class="text-decoration-none">
|
||||
{{ req.fleet_unit.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ req.part_name }}</td>
|
||||
<td>{{ req.quantity }}</td>
|
||||
<td>
|
||||
<span class="badge rounded-pill bg-{{ req.status|yesno:'info,secondary' }}">
|
||||
{{ req.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="btn-group">
|
||||
<a href="#" class="btn btn-sm btn-outline-primary">Управление</a>
|
||||
{% if user.is_staff %}
|
||||
<form action="{% url 'part_request_delete' req.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить заявку?')">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm btn-outline-danger ms-1" title="Удалить">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="6" class="text-center py-4">Нет активных заявок</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover align-middle mb-0">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="ps-4">Дата</th>
|
||||
<th>Техника</th>
|
||||
<th>Деталь</th>
|
||||
<th>Статус</th>
|
||||
<th class="pe-4 text-end">Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for req in requests %}
|
||||
<tr>
|
||||
<td class="ps-4 small text-secondary-apple">{{ req.created_at|date:"d.m.Y" }}</td>
|
||||
<td>
|
||||
<a href="{% url 'fleet_detail' req.fleet_unit.pk %}" class="text-decoration-none fw-bold text-dark">
|
||||
{{ req.fleet_unit.name }}
|
||||
</a>
|
||||
</td>
|
||||
<td>{{ req.part_name }} <span class="text-secondary-apple small">x{{ req.quantity }}</span></td>
|
||||
<td>
|
||||
<span class="badge bg-light text-dark border-0 shadow-sm">
|
||||
{{ req.get_status_display }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="pe-4 text-end">
|
||||
<div class="d-flex justify-content-end align-items-center">
|
||||
<a href="#" class="btn btn-sm btn-link text-decoration-none me-2">Управление</a>
|
||||
{% if user.is_staff %}
|
||||
<form action="{% url 'part_request_delete' req.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить заявку?')">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
<td colspan="5" class="text-center py-5 text-secondary-apple">Активных заявок нет</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Suppliers Section -->
|
||||
<div class="col-lg-4">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-header bg-white py-3 d-flex justify-content-between align-items-center">
|
||||
<h5 class="card-title mb-0">Поставщики</h5>
|
||||
<div class="card">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="fw-bold mb-0">Контрагенты</h5>
|
||||
<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>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="list-group list-group-flush">
|
||||
{% for supplier in suppliers %}
|
||||
<div class="list-group-item px-0 py-3">
|
||||
<div class="d-flex w-100 justify-content-between align-items-center">
|
||||
<h6 class="mb-1">{{ supplier.name }}</h6>
|
||||
<div class="btn-group">
|
||||
<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>
|
||||
</a>
|
||||
{% if user.is_staff %}
|
||||
<form action="{% url 'supplier_delete' supplier.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить поставщика?')">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm btn-link p-0 text-danger" title="Удалить">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="list-group list-group-flush">
|
||||
{% for supplier in suppliers %}
|
||||
<div class="list-group-item">
|
||||
<div class="d-flex w-100 justify-content-between align-items-start mb-2">
|
||||
<h6 class="fw-bold mb-0 text-dark">{{ supplier.name }}</h6>
|
||||
<div class="d-flex gap-2">
|
||||
<a href="{% url 'supplier_edit' supplier.pk %}" class="text-secondary-apple" title="Редактировать">
|
||||
<i class="bi bi-pencil-square"></i>
|
||||
</a>
|
||||
{% if user.is_staff %}
|
||||
<form action="{% url 'supplier_delete' supplier.pk %}" method="POST" class="d-inline" onsubmit="return confirm('Удалить поставщика?')">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm text-danger border-0 bg-transparent p-0" title="Удалить">
|
||||
<i class="bi bi-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
</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>
|
||||
{% empty %}
|
||||
<div class="text-center py-3 text-muted">Список поставщиков пуст</div>
|
||||
{% endfor %}
|
||||
<div class="small text-secondary-apple">
|
||||
<div class="mb-1"><i class="bi bi-person me-2"></i>{{ supplier.representative_name|default:"-" }}</div>
|
||||
<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>
|
||||
{% empty %}
|
||||
<div class="text-center py-5 text-secondary-apple">Список пуст</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
@ -20,6 +20,8 @@ urlpatterns = [
|
||||
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>/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
|
||||
path('breakdown/', views.BreakdownListView.as_view(), name='breakdown_list'),
|
||||
|
||||
133
core/views.py
133
core/views.py
@ -11,7 +11,7 @@ from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
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
|
||||
class StaffRequiredMixin(UserPassesTestMixin):
|
||||
@ -73,6 +73,16 @@ class MaintenanceForm(forms.ModelForm):
|
||||
'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 Meta:
|
||||
model = Breakdown
|
||||
@ -181,6 +191,11 @@ class MaintenanceDetailView(DetailView):
|
||||
template_name = 'core/maintenance_detail.html'
|
||||
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):
|
||||
model = Maintenance
|
||||
template_name = 'core/maintenance_form.html'
|
||||
@ -248,9 +263,27 @@ class MaintenanceCompleteView(View):
|
||||
|
||||
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):
|
||||
def get(self, request, pk):
|
||||
maintenance = get_object_or_404(Maintenance, pk=pk)
|
||||
unit = maintenance.fleet_unit
|
||||
buffer = BytesIO()
|
||||
p = canvas.Canvas(buffer, pagesize=A4)
|
||||
|
||||
@ -262,19 +295,103 @@ class MaintenancePDFView(View):
|
||||
except:
|
||||
pass # Fallback to default if font not found
|
||||
|
||||
p.drawString(100, 800, f"Акт технического обслуживания - {maintenance.m_type}")
|
||||
p.drawString(100, 780, f"Техника: {maintenance.fleet_unit.name}")
|
||||
p.drawString(100, 760, f"Дата: {maintenance.actual_date}")
|
||||
p.drawString(100, 740, f"Наработка: {maintenance.actual_runtime}")
|
||||
# Header
|
||||
p.setFont('LiberationSans', 16)
|
||||
p.drawString(100, 800, f"Акт технического обслуживания: {maintenance.m_type}")
|
||||
|
||||
y = 700
|
||||
p.drawString(100, y, "Чек-лист:")
|
||||
p.setFont('LiberationSans', 10)
|
||||
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
|
||||
p.setFont('LiberationSans', 10)
|
||||
for item in maintenance.checklist:
|
||||
status = "[x]" if item['done'] else "[ ]"
|
||||
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.save()
|
||||
|
||||
|
||||
@ -1,116 +1,191 @@
|
||||
:root {
|
||||
--bg-slate-900: #1e293b;
|
||||
--text-blue-500: #3b82f6;
|
||||
--primary: #3b82f6;
|
||||
--secondary: #64748b;
|
||||
--success: #10b981;
|
||||
--danger: #ef4444;
|
||||
--warning: #f59e0b;
|
||||
--info: #06b6d4;
|
||||
--apple-bg: #f5f5f7;
|
||||
--apple-card-bg: #ffffff;
|
||||
--apple-blue: #0071e3;
|
||||
--apple-text: #1d1d1f;
|
||||
--apple-text-secondary: #86868b;
|
||||
--apple-border: #d2d2d7;
|
||||
--primary: #0071e3;
|
||||
--secondary: #86868b;
|
||||
--success: #28cd41;
|
||||
--danger: #ff3b30;
|
||||
--warning: #ff9f0a;
|
||||
--info: #55bef0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: #334155;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: var(--apple-bg);
|
||||
color: var(--apple-text);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-weight: 700;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.022em;
|
||||
}
|
||||
|
||||
.bg-slate-900 {
|
||||
background-color: var(--bg-slate-900) !important;
|
||||
/* Glassmorphism Navbar */
|
||||
.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 {
|
||||
color: var(--text-blue-500) !important;
|
||||
.navbar-apple .nav-link {
|
||||
color: var(--apple-text) !important;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.tracking-wider {
|
||||
letter-spacing: 0.05em;
|
||||
.navbar-apple .nav-link:hover {
|
||||
color: var(--apple-blue) !important;
|
||||
}
|
||||
|
||||
/* Card Styling */
|
||||
.navbar-apple .navbar-brand {
|
||||
color: var(--apple-text) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Apple Cards */
|
||||
.card {
|
||||
background: var(--apple-card-bg);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
border-radius: 20px;
|
||||
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 {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important;
|
||||
transform: scale(1.01);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* Stat Cards */
|
||||
.stat-card {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.card-header {
|
||||
background: transparent;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
padding: 1.25rem 1.5rem;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
/* Buttons */
|
||||
.btn {
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
margin-right: 1rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.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 */
|
||||
.badge {
|
||||
padding: 0.5em 0.8em;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
border-radius: 8px;
|
||||
padding: 0.4em 0.8em;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero-gradient {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: white;
|
||||
padding: 3rem 0;
|
||||
border-radius: 16px;
|
||||
/* Stat Cards Customization */
|
||||
.stat-card {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Form Styling */
|
||||
.form-control, .form-select {
|
||||
border-radius: 8px;
|
||||
padding: 0.625rem 0.75rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
.text-secondary-apple {
|
||||
color: var(--apple-text-secondary) !important;
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
border-color: #3b82f6;
|
||||
/* Table styling */
|
||||
.table {
|
||||
--bs-table-bg: transparent;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: 8px;
|
||||
padding: 0.625rem 1.25rem;
|
||||
font-weight: 500;
|
||||
.table thead th {
|
||||
font-weight: 600;
|
||||
color: var(--apple-text-secondary);
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
border-bottom: 1px solid var(--apple-border);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--primary);
|
||||
border-color: var(--primary);
|
||||
/* List group */
|
||||
.list-group-item {
|
||||
border-color: rgba(0, 0, 0, 0.05);
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #2563eb;
|
||||
border-color: #2563eb;
|
||||
.list-group-item:first-child {
|
||||
border-top-left-radius: 20px;
|
||||
border-top-right-radius: 20px;
|
||||
}
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.stat-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
.hero-gradient {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
}
|
||||
.list-group-item:last-child {
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar for modern look */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d2d2d7;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #86868b;
|
||||
}
|
||||
|
||||
@ -1,116 +1,191 @@
|
||||
:root {
|
||||
--bg-slate-900: #1e293b;
|
||||
--text-blue-500: #3b82f6;
|
||||
--primary: #3b82f6;
|
||||
--secondary: #64748b;
|
||||
--success: #10b981;
|
||||
--danger: #ef4444;
|
||||
--warning: #f59e0b;
|
||||
--info: #06b6d4;
|
||||
--apple-bg: #f5f5f7;
|
||||
--apple-card-bg: #ffffff;
|
||||
--apple-blue: #0071e3;
|
||||
--apple-text: #1d1d1f;
|
||||
--apple-text-secondary: #86868b;
|
||||
--apple-border: #d2d2d7;
|
||||
--primary: #0071e3;
|
||||
--secondary: #86868b;
|
||||
--success: #28cd41;
|
||||
--danger: #ff3b30;
|
||||
--warning: #ff9f0a;
|
||||
--info: #55bef0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
color: #334155;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", "Helvetica Neue", Arial, sans-serif;
|
||||
background-color: var(--apple-bg);
|
||||
color: var(--apple-text);
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: 'Montserrat', sans-serif;
|
||||
font-weight: 700;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.022em;
|
||||
}
|
||||
|
||||
.bg-slate-900 {
|
||||
background-color: var(--bg-slate-900) !important;
|
||||
/* Glassmorphism Navbar */
|
||||
.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 {
|
||||
color: var(--text-blue-500) !important;
|
||||
.navbar-apple .nav-link {
|
||||
color: var(--apple-text) !important;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.tracking-wider {
|
||||
letter-spacing: 0.05em;
|
||||
.navbar-apple .nav-link:hover {
|
||||
color: var(--apple-blue) !important;
|
||||
}
|
||||
|
||||
/* Card Styling */
|
||||
.navbar-apple .navbar-brand {
|
||||
color: var(--apple-text) !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Apple Cards */
|
||||
.card {
|
||||
background: var(--apple-card-bg);
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
border-radius: 20px;
|
||||
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 {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05) !important;
|
||||
transform: scale(1.01);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
|
||||
/* Stat Cards */
|
||||
.stat-card {
|
||||
padding: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.card-header {
|
||||
background: transparent;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
|
||||
padding: 1.25rem 1.5rem;
|
||||
}
|
||||
|
||||
.stat-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
/* Buttons */
|
||||
.btn {
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
margin-right: 1rem;
|
||||
padding: 0.6rem 1.2rem;
|
||||
font-size: 0.95rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.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 */
|
||||
.badge {
|
||||
padding: 0.5em 0.8em;
|
||||
font-weight: 500;
|
||||
border-radius: 6px;
|
||||
border-radius: 8px;
|
||||
padding: 0.4em 0.8em;
|
||||
font-weight: 600;
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero-gradient {
|
||||
background: linear-gradient(135deg, #1e293b 0%, #334155 100%);
|
||||
color: white;
|
||||
padding: 3rem 0;
|
||||
border-radius: 16px;
|
||||
/* Stat Cards Customization */
|
||||
.stat-card {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.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;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Form Styling */
|
||||
.form-control, .form-select {
|
||||
border-radius: 8px;
|
||||
padding: 0.625rem 0.75rem;
|
||||
border: 1px solid #e2e8f0;
|
||||
.text-secondary-apple {
|
||||
color: var(--apple-text-secondary) !important;
|
||||
}
|
||||
|
||||
.form-control:focus, .form-select:focus {
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
border-color: #3b82f6;
|
||||
/* Table styling */
|
||||
.table {
|
||||
--bs-table-bg: transparent;
|
||||
}
|
||||
|
||||
.btn {
|
||||
border-radius: 8px;
|
||||
padding: 0.625rem 1.25rem;
|
||||
font-weight: 500;
|
||||
.table thead th {
|
||||
font-weight: 600;
|
||||
color: var(--apple-text-secondary);
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
border-bottom: 1px solid var(--apple-border);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: var(--primary);
|
||||
border-color: var(--primary);
|
||||
/* List group */
|
||||
.list-group-item {
|
||||
border-color: rgba(0, 0, 0, 0.05);
|
||||
padding: 1rem 1.5rem;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background-color: #2563eb;
|
||||
border-color: #2563eb;
|
||||
.list-group-item:first-child {
|
||||
border-top-left-radius: 20px;
|
||||
border-top-right-radius: 20px;
|
||||
}
|
||||
|
||||
/* Mobile adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.stat-card {
|
||||
padding: 1rem;
|
||||
}
|
||||
.hero-gradient {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
}
|
||||
.list-group-item:last-child {
|
||||
border-bottom-left-radius: 20px;
|
||||
border-bottom-right-radius: 20px;
|
||||
}
|
||||
|
||||
/* Custom Scrollbar for modern look */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #d2d2d7;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #86868b;
|
||||
}
|
||||
|
||||
BIN
staticfiles/pasted-20260127-202812-8ab5fa36.png
Normal file
BIN
staticfiles/pasted-20260127-202812-8ab5fa36.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 363 KiB |
Loading…
x
Reference in New Issue
Block a user