diff --git a/assets/pasted-20260127-200020-e0bae63b.png b/assets/pasted-20260127-200020-e0bae63b.png new file mode 100644 index 0000000..1c8cf34 Binary files /dev/null and b/assets/pasted-20260127-200020-e0bae63b.png differ diff --git a/assets/pasted-20260127-200500-2ad7f024.png b/assets/pasted-20260127-200500-2ad7f024.png new file mode 100644 index 0000000..27998c4 Binary files /dev/null and b/assets/pasted-20260127-200500-2ad7f024.png differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 96bce55..850453c 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/__pycache__/urls.cpython-311.pyc b/config/__pycache__/urls.cpython-311.pyc index 0b85e94..9d7c63d 100644 Binary files a/config/__pycache__/urls.cpython-311.pyc and b/config/__pycache__/urls.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 291d043..cb8d3b1 100644 --- a/config/settings.py +++ b/config/settings.py @@ -149,6 +149,9 @@ STATIC_URL = 'static/' # Collect static into a separate folder; avoid overlapping with STATICFILES_DIRS. STATIC_ROOT = BASE_DIR / 'staticfiles' +MEDIA_URL = 'media/' +MEDIA_ROOT = BASE_DIR / 'media' + STATICFILES_DIRS = [ BASE_DIR / 'static', BASE_DIR / 'assets', diff --git a/config/urls.py b/config/urls.py index bcfc074..1a48858 100644 --- a/config/urls.py +++ b/config/urls.py @@ -27,3 +27,4 @@ urlpatterns = [ if settings.DEBUG: urlpatterns += static("/assets/", document_root=settings.BASE_DIR / "assets") urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index edb7428..31f12a7 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 91a030d..1ab4b3e 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index 7d8dc91..ec14df2 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index b0cfab8..f599451 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index a0a056c..e5cabca 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,15 +1,36 @@ from django.contrib import admin -from .models import Category, FleetUnit, Maintenance, Breakdown, PartRequest, Document +from .models import Category, FleetUnit, Maintenance, Breakdown, PartRequest, Document, Supplier @admin.register(Category) class CategoryAdmin(admin.ModelAdmin): list_display = ('name',) +@admin.register(Supplier) +class SupplierAdmin(admin.ModelAdmin): + list_display = ('name', 'representative_name', 'phone', 'email', 'contract_number') + search_fields = ('name', 'representative_name', 'contract_number') + @admin.register(FleetUnit) class FleetUnitAdmin(admin.ModelAdmin): list_display = ('name', 'category', 'plate_number', 'status', 'year') list_filter = ('status', 'category') search_fields = ('name', 'vin', 'plate_number') + + fieldsets = ( + ('Основная информация', { + 'fields': ('name', 'category', 'model_name', 'vin', 'plate_number', 'year', 'photo', 'status', 'commissioning_date', 'notes') + }), + ('Страховка', { + 'fields': ('insurance_company', 'insurance_policy_number', 'insurance_start_date', 'insurance_end_date') + }), + ('Снабжение и Поставщик', { + 'fields': ('supplier', 'vehicle_documents', 'supplier_name', 'supplier_contacts') + }), + ('QR-код', { + 'fields': ('qr_code',), + 'classes': ('collapse',) + }), + ) @admin.register(Maintenance) class MaintenanceAdmin(admin.ModelAdmin): diff --git a/core/migrations/0004_fleetunit_insurance_company_and_more.py b/core/migrations/0004_fleetunit_insurance_company_and_more.py new file mode 100644 index 0000000..ab01502 --- /dev/null +++ b/core/migrations/0004_fleetunit_insurance_company_and_more.py @@ -0,0 +1,43 @@ +# Generated by Django 5.2.7 on 2026-01-27 20:02 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0003_setup_groups'), + ] + + operations = [ + migrations.AddField( + model_name='fleetunit', + name='insurance_company', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Страховая компания'), + ), + migrations.AddField( + model_name='fleetunit', + name='insurance_end_date', + field=models.DateField(blank=True, null=True, verbose_name='Дата окончания страховки'), + ), + migrations.AddField( + model_name='fleetunit', + name='insurance_start_date', + field=models.DateField(blank=True, null=True, verbose_name='Дата начала страховки'), + ), + migrations.AddField( + model_name='fleetunit', + name='supplier_contacts', + field=models.TextField(blank=True, null=True, verbose_name='Контакты поставщика'), + ), + migrations.AddField( + model_name='fleetunit', + name='supplier_name', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Поставщик / Контрагент'), + ), + migrations.AddField( + model_name='fleetunit', + name='vehicle_documents', + field=models.FileField(blank=True, null=True, upload_to='fleet_docs/', verbose_name='Документы на авто'), + ), + ] diff --git a/core/migrations/0005_supplier_fleetunit_insurance_policy_number_and_more.py b/core/migrations/0005_supplier_fleetunit_insurance_policy_number_and_more.py new file mode 100644 index 0000000..723d669 --- /dev/null +++ b/core/migrations/0005_supplier_fleetunit_insurance_policy_number_and_more.py @@ -0,0 +1,49 @@ +# Generated by Django 5.2.7 on 2026-01-27 20:11 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0004_fleetunit_insurance_company_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Supplier', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=255, verbose_name='Наименование компании')), + ('representative_name', models.CharField(blank=True, max_length=255, null=True, verbose_name='ФИО представителя/менеджера')), + ('phone', models.CharField(blank=True, max_length=50, null=True, verbose_name='Телефон')), + ('email', models.EmailField(blank=True, max_length=254, null=True, verbose_name='Электронная почта')), + ('contract_number', models.CharField(blank=True, max_length=100, null=True, verbose_name='Номер договора')), + ], + options={ + 'verbose_name': 'Поставщик / Контрагент', + 'verbose_name_plural': 'Поставщики / Контрагенты', + }, + ), + migrations.AddField( + model_name='fleetunit', + name='insurance_policy_number', + field=models.CharField(blank=True, max_length=100, null=True, verbose_name='Номер страхового полиса'), + ), + migrations.AlterField( + model_name='fleetunit', + name='supplier_contacts', + field=models.TextField(blank=True, null=True, verbose_name='Контакты поставщика (старое)'), + ), + migrations.AlterField( + model_name='fleetunit', + name='supplier_name', + field=models.CharField(blank=True, max_length=255, null=True, verbose_name='Поставщик (старое)'), + ), + migrations.AddField( + model_name='fleetunit', + name='supplier', + field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='core.supplier', verbose_name='Поставщик / Контрагент'), + ), + ] diff --git a/core/migrations/__pycache__/0004_fleetunit_insurance_company_and_more.cpython-311.pyc b/core/migrations/__pycache__/0004_fleetunit_insurance_company_and_more.cpython-311.pyc new file mode 100644 index 0000000..4a9a6e9 Binary files /dev/null and b/core/migrations/__pycache__/0004_fleetunit_insurance_company_and_more.cpython-311.pyc differ diff --git a/core/migrations/__pycache__/0005_supplier_fleetunit_insurance_policy_number_and_more.cpython-311.pyc b/core/migrations/__pycache__/0005_supplier_fleetunit_insurance_policy_number_and_more.cpython-311.pyc new file mode 100644 index 0000000..a9628e3 Binary files /dev/null and b/core/migrations/__pycache__/0005_supplier_fleetunit_insurance_policy_number_and_more.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 41ce768..6482c08 100644 --- a/core/models.py +++ b/core/models.py @@ -16,6 +16,20 @@ class Category(models.Model): def __str__(self): return self.name +class Supplier(models.Model): + name = models.CharField(max_length=255, verbose_name="Наименование компании") + representative_name = models.CharField(max_length=255, blank=True, null=True, verbose_name="ФИО представителя/менеджера") + phone = models.CharField(max_length=50, blank=True, null=True, verbose_name="Телефон") + email = models.EmailField(blank=True, null=True, verbose_name="Электронная почта") + contract_number = models.CharField(max_length=100, blank=True, null=True, verbose_name="Номер договора") + + class Meta: + verbose_name = "Поставщик / Контрагент" + verbose_name_plural = "Поставщики / Контрагенты" + + def __str__(self): + return self.name + class FleetUnit(models.Model): STATUS_CHOICES = [ ('active', 'В работе'), @@ -37,6 +51,18 @@ class FleetUnit(models.Model): notes = models.TextField(blank=True, null=True, verbose_name="Примечания") qr_code = models.ImageField(upload_to='qrcodes/', blank=True, null=True, verbose_name="QR-код") + # Insurance + insurance_company = models.CharField(max_length=255, blank=True, null=True, verbose_name="Страховая компания") + insurance_policy_number = models.CharField(max_length=100, blank=True, null=True, verbose_name="Номер страхового полиса") + insurance_start_date = models.DateField(blank=True, null=True, verbose_name="Дата начала страховки") + insurance_end_date = models.DateField(blank=True, null=True, verbose_name="Дата окончания страховки") + + # Supplier + supplier = models.ForeignKey(Supplier, on_delete=models.SET_NULL, null=True, blank=True, verbose_name="Поставщик / Контрагент") + supplier_name = models.CharField(max_length=255, blank=True, null=True, verbose_name="Поставщик (старое)") + supplier_contacts = models.TextField(blank=True, null=True, verbose_name="Контакты поставщика (старое)") + vehicle_documents = models.FileField(upload_to="fleet_docs/", blank=True, null=True, verbose_name="Документы на авто") + created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) @@ -68,13 +94,8 @@ class FleetUnit(models.Model): self.generate_qr_code() def generate_qr_code(self): - # We need the full URL. In a real app, we'd use the site domain. - # For now, we'll use a relative path or a placeholder if domain is unknown. path = self.get_absolute_url() - # You might want to use a full URL here if you have a domain - # base_url = "https://yourdomain.com" - # qr_data = f"{base_url}{path}" - qr_data = path # Using path for now, or you can try to get site domain + qr_data = path qr = qrcode.QRCode(version=1, box_size=10, border=5) qr.add_data(qr_data) @@ -194,4 +215,4 @@ 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}" \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 7f6a241..8f4dbf0 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -46,6 +46,9 @@ +
@@ -81,4 +84,4 @@ {% block extra_js %}{% endblock %} - + \ No newline at end of file diff --git a/core/templates/core/breakdown_list.html b/core/templates/core/breakdown_list.html new file mode 100644 index 0000000..eba1564 --- /dev/null +++ b/core/templates/core/breakdown_list.html @@ -0,0 +1,62 @@ +{% extends 'base.html' %} +{% load static %} + +{% block content %} +
+
+

Журнал поломок

+ + Сообщить о поломке + +
+ +
+
+ + + + + + + + + + + + {% for b in breakdowns %} + + + + + + + + {% empty %} + + + + {% endfor %} + +
ТехникаДатаУзел / СистемаСтатусДействия
+ +
{{ b.fleet_unit.plate_number|default:b.fleet_unit.vin }}
+
{{ b.date }}{{ b.system_node }} + {% if b.status == 'reported' %} + Заявлено + {% elif b.status == 'repaired' %} + Отремонтировано + {% elif b.status == 'need_part' %} + Нужна деталь + {% else %} + {{ b.status }} + {% endif %} + + К технике + {% if b.status == 'need_part' %} + Заказать запчасть + {% endif %} +
Нет зафиксированных поломок
+
+
+
+{% endblock %} diff --git a/core/templates/core/fleet_detail.html b/core/templates/core/fleet_detail.html index 448a6d1..4c52ca4 100644 --- a/core/templates/core/fleet_detail.html +++ b/core/templates/core/fleet_detail.html @@ -40,6 +40,15 @@
+ {% if unit.insurance_company %} +
+
Страховка
+

Компания: {{ unit.insurance_company }}

+

Полис: {{ unit.insurance_policy_number|default:"-" }}

+

Срок: {{ unit.insurance_start_date|date:"d.m.Y" }} — {{ unit.insurance_end_date|date:"d.m.Y" }}

+
+ {% endif %} +
Редактировать Заявить о поломке @@ -73,6 +82,9 @@ +
@@ -169,6 +181,64 @@
+ + +
+
Данные по снабжению и поставщику
+
+
+ {% if unit.supplier %} +

Поставщик / Контрагент

+

{{ unit.supplier.name }}

+

Договор №{{ unit.supplier.contract_number|default:"-" }}

+ +

Представитель

+

{{ unit.supplier.representative_name|default:"-" }}

+

{{ unit.supplier.phone|default:"-" }}

+

{{ unit.supplier.email|default:"-" }}

+ {% else %} +

Поставщик не привязан

+

Вы можете выбрать поставщика в режиме редактирования техники.

+ {% endif %} +
+
+

Документы на авто

+ {% if unit.vehicle_documents %} + + Открыть документы + + {% else %} +

Не загружены

+ {% endif %} +
+
+ +
+ +
Связанные заявки на запчасти
+
+ + + + + + + + + + {% for r in part_requests %} + + + + + + {% empty %} + + {% endfor %} + +
ДетальСтатусДата
{{ r.part_name }}{{ r.get_status_display }}{{ r.created_at|date:"d.m.Y" }}
Нет заявок
+
+
@@ -182,4 +252,4 @@ -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/core/templates/core/fleet_form.html b/core/templates/core/fleet_form.html index d9bc7eb..d82f33e 100644 --- a/core/templates/core/fleet_form.html +++ b/core/templates/core/fleet_form.html @@ -1,115 +1,112 @@ {% extends 'base.html' %} -{% load static %} - -{% block title %} - {% if object %}Редактирование {{ object.name }}{% else %}Добавление техники{% endif %} | Fleet Manager -{% endblock %} {% block content %} -
-
-
- -

{% if object %}Редактирование {{ object.name }}{% else %}Новая единица техники{% endif %}

-
+
+
+
+
+
+

+ {% if object %}Редактировать технику{% else %}Добавить новую технику{% endif %} +

+
+
+
+ {% csrf_token %} + + +
Основная информация
+
+
+ + {{ form.name }} +
+
+ + {{ form.model_name }} +
+
+ + {{ form.category }} +
+
+ + {{ form.vin }} +
+
+ + {{ form.plate_number }} +
+
+ + {{ form.year }} +
+
+ + {{ form.commissioning_date }} +
+
+ + {{ form.status }} +
+
-
-
- - {% csrf_token %} - -
-
- - {{ form.name }} - {% if form.name.errors %}
{{ form.name.errors }}
{% endif %} + +
Страховка
+
+
+ + {{ form.insurance_company }} +
+
+ + {{ form.insurance_policy_number }} +
+
+ + {{ form.insurance_start_date }} +
+
+ + {{ form.insurance_end_date }} +
- -
- - {{ form.category }} - {% if form.category.errors %}
{{ form.category.errors }}
{% endif %} + + +
Снабжение
+
+
+ + {{ form.supplier }} +
Выберите поставщика из списка. Создать нового можно в разделе "Снабжение".
+
+
+ + {{ form.vehicle_documents }} +
- -
- - {{ form.model_name }} - {% if form.model_name.errors %}
{{ form.model_name.errors }}
{% endif %} + + +
Медиа и примечания
+
+
+ + {{ form.photo }} +
+
+ + {{ form.notes }} +
- -
- - {{ form.vin }} - {% if form.vin.errors %}
{{ form.vin.errors }}
{% endif %} + +
+ Отмена +
- -
- - {{ form.plate_number }} - {% if form.plate_number.errors %}
{{ form.plate_number.errors }}
{% endif %} -
- -
- - {{ form.year }} - {% if form.year.errors %}
{{ form.year.errors }}
{% endif %} -
- -
- - {{ form.status }} - {% if form.status.errors %}
{{ form.status.errors }}
{% endif %} -
- -
- - {{ form.commissioning_date }} -
ГГГГ-ММ-ДД
- {% if form.commissioning_date.errors %}
{{ form.commissioning_date.errors }}
{% endif %} -
- -
- - {{ form.photo }} - {% if form.photo.errors %}
{{ form.photo.errors }}
{% endif %} -
- -
- - {{ form.notes }} - {% if form.notes.errors %}
{{ form.notes.errors }}
{% endif %} -
-
- -
- - Отмена -
- + +
- - {% endblock %} diff --git a/core/templates/core/supplier_form.html b/core/templates/core/supplier_form.html new file mode 100644 index 0000000..b880311 --- /dev/null +++ b/core/templates/core/supplier_form.html @@ -0,0 +1,70 @@ +{% extends 'base.html' %} +{% load crispy_forms_tags %} + +{% block content %} +
+
+
+
+
+

+ {% if object %}Редактировать поставщика{% else %}Новый поставщик{% endif %} +

+
+
+
+ {% csrf_token %} +
+ + {{ form.name }} + {% if form.name.errors %}
{{ form.name.errors }}
{% endif %} +
+
+
+ + {{ form.representative_name }} +
+
+ + {{ form.phone }} +
+
+
+
+ + {{ form.email }} +
+
+ + {{ form.contract_number }} +
+
+ +
+ Отмена + +
+
+
+
+
+
+
+ + +{% endblock %} diff --git a/core/templates/core/supply_list.html b/core/templates/core/supply_list.html new file mode 100644 index 0000000..caea49b --- /dev/null +++ b/core/templates/core/supply_list.html @@ -0,0 +1,96 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Снабжение

+
+ +
+ +
+
+
+
Заявки на запчасти
+
+
+
+ + + + + + + + + + + + + {% for req in requests %} + + + + + + + + + {% empty %} + + + + {% endfor %} + +
ДатаТехникаДетальКол-воСтатусДействия
{{ req.created_at|date:"d.m.Y" }} + + {{ req.fleet_unit.name }} + + {{ req.part_name }}{{ req.quantity }} + + {{ req.get_status_display }} + + + Управление +
Нет активных заявок
+
+
+
+
+ + +
+
+
+
Поставщики
+ + Добавить + +
+
+
+ {% for supplier in suppliers %} +
+
+
{{ supplier.name }}
+ + + +
+

+ Менеджер: {{ supplier.representative_name|default:"-" }}
+ Тел: {{ supplier.phone|default:"-" }}
+ Email: {{ supplier.email|default:"-" }}
+ Договор: {{ supplier.contract_number|default:"-" }} +

+
+ {% empty %} +
Список поставщиков пуст
+ {% endfor %} +
+
+
+
+
+
+{% endblock %} diff --git a/core/urls.py b/core/urls.py index 50809ad..939eb6b 100644 --- a/core/urls.py +++ b/core/urls.py @@ -26,4 +26,9 @@ urlpatterns = [ # Part Request path('part-request/', views.PartRequestListView.as_view(), name='part_request_list'), path('part-request/add/', views.PartRequestCreateView.as_view(), name='part_request_add'), + + # Supply + path('supply/', views.SupplyListView.as_view(), name='supply_list'), + path('supplier/add/', views.SupplierCreateView.as_view(), name='supplier_add'), + path('supplier//edit/', views.SupplierUpdateView.as_view(), name='supplier_edit'), ] \ No newline at end of file diff --git a/core/views.py b/core/views.py index 6706a39..282ff82 100644 --- a/core/views.py +++ b/core/views.py @@ -11,16 +11,47 @@ 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 +from .models import FleetUnit, Maintenance, Breakdown, PartRequest, Category, Document, Supplier # Forms class FleetUnitForm(forms.ModelForm): class Meta: model = FleetUnit - fields = ['name', 'category', 'model_name', 'vin', 'plate_number', 'year', 'photo', 'status', 'commissioning_date', 'notes'] + fields = [ + 'name', 'category', 'model_name', 'vin', 'plate_number', 'year', 'photo', 'status', + 'commissioning_date', 'notes', + 'insurance_company', 'insurance_policy_number', 'insurance_start_date', 'insurance_end_date', + 'supplier', 'vehicle_documents' + ] widgets = { - 'commissioning_date': forms.DateInput(attrs={'type': 'date'}), - 'notes': forms.Textarea(attrs={'rows': 3}), + 'name': forms.TextInput(attrs={'class': 'form-control'}), + 'category': forms.Select(attrs={'class': 'form-select'}), + 'model_name': forms.TextInput(attrs={'class': 'form-control'}), + 'vin': forms.TextInput(attrs={'class': 'form-control'}), + 'plate_number': forms.TextInput(attrs={'class': 'form-control'}), + 'year': forms.NumberInput(attrs={'class': 'form-control'}), + 'photo': forms.FileInput(attrs={'class': 'form-control'}), + 'status': forms.Select(attrs={'class': 'form-select'}), + 'commissioning_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), + 'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), + 'insurance_company': forms.TextInput(attrs={'class': 'form-control'}), + 'insurance_policy_number': forms.TextInput(attrs={'class': 'form-control'}), + 'insurance_start_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), + 'insurance_end_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), + 'supplier': forms.Select(attrs={'class': 'form-select'}), + 'vehicle_documents': forms.FileInput(attrs={'class': 'form-control'}), + } + +class SupplierForm(forms.ModelForm): + class Meta: + model = Supplier + fields = ['name', 'representative_name', 'phone', 'email', 'contract_number'] + widgets = { + 'name': forms.TextInput(attrs={'class': 'form-control'}), + 'representative_name': forms.TextInput(attrs={'class': 'form-control'}), + 'phone': forms.TextInput(attrs={'class': 'form-control'}), + 'email': forms.EmailInput(attrs={'class': 'form-control'}), + 'contract_number': forms.TextInput(attrs={'class': 'form-control'}), } class MaintenanceForm(forms.ModelForm): @@ -28,8 +59,12 @@ class MaintenanceForm(forms.ModelForm): model = Maintenance fields = ['fleet_unit', 'm_type', 'planned_date', 'planned_runtime', 'mechanic', 'notes'] widgets = { - 'planned_date': forms.DateInput(attrs={'type': 'date'}), - 'notes': forms.Textarea(attrs={'rows': 3}), + 'fleet_unit': forms.Select(attrs={'class': 'form-select'}), + 'm_type': forms.Select(attrs={'class': 'form-select'}), + 'planned_date': forms.DateInput(attrs={'type': 'date', 'class': 'form-control'}), + 'planned_runtime': forms.NumberInput(attrs={'class': 'form-control'}), + 'mechanic': forms.Select(attrs={'class': 'form-select'}), + 'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), } class BreakdownForm(forms.ModelForm): @@ -37,16 +72,27 @@ class BreakdownForm(forms.ModelForm): model = Breakdown fields = ['fleet_unit', 'system_node', 'description', 'photo', 'status', 'notes'] widgets = { - 'description': forms.Textarea(attrs={'rows': 3}), - 'notes': forms.Textarea(attrs={'rows': 3}), + 'fleet_unit': forms.Select(attrs={'class': 'form-select'}), + 'system_node': forms.TextInput(attrs={'class': 'form-control'}), + 'description': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), + 'photo': forms.FileInput(attrs={'class': 'form-control'}), + 'status': forms.Select(attrs={'class': 'form-select'}), + 'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), } class PartRequestForm(forms.ModelForm): class Meta: model = PartRequest - fields = ['fleet_unit', 'breakdown', 'part_name', 'article_number', 'quantity', 'photo', 'notes'] + fields = ['fleet_unit', 'breakdown', 'part_name', 'article_number', 'quantity', 'status', 'photo', 'notes'] widgets = { - 'notes': forms.Textarea(attrs={'rows': 3}), + 'fleet_unit': forms.Select(attrs={'class': 'form-select'}), + 'breakdown': forms.Select(attrs={'class': 'form-select'}), + 'part_name': forms.TextInput(attrs={'class': 'form-control'}), + 'article_number': forms.TextInput(attrs={'class': 'form-control'}), + 'quantity': forms.NumberInput(attrs={'class': 'form-control'}), + 'status': forms.Select(attrs={'class': 'form-select'}), + 'photo': forms.FileInput(attrs={'class': 'form-control'}), + 'notes': forms.Textarea(attrs={'rows': 3, 'class': 'form-control'}), } # Views @@ -194,15 +240,21 @@ class MaintenancePDFView(View): buffer = BytesIO() p = canvas.Canvas(buffer, pagesize=A4) - # Draw PDF content (basic for now as we don't have Cyrillic fonts loaded by default in reportlab without setup) - # Note: For real Cyrillic support we need to register a TTF font. - p.drawString(100, 800, f"Maintenance Act - {maintenance.m_type}") - p.drawString(100, 780, f"Unit: {maintenance.fleet_unit.name}") - p.drawString(100, 760, f"Date: {maintenance.actual_date}") - p.drawString(100, 740, f"Runtime: {maintenance.actual_runtime}") + # Register Cyrillic font + font_path = "/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf" + try: + pdfmetrics.registerFont(TTFont('LiberationSans', font_path)) + p.setFont('LiberationSans', 12) + 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}") y = 700 - p.drawString(100, y, "Checklist:") + p.drawString(100, y, "Чек-лист:") y -= 20 for item in maintenance.checklist: status = "[x]" if item['done'] else "[ ]" @@ -271,4 +323,26 @@ class PartRequestCreateView(CreateView): return initial def get_success_url(self): - return reverse('fleet_detail', kwargs={'pk': self.object.fleet_unit.pk}) \ No newline at end of file + return reverse('fleet_detail', kwargs={'pk': self.object.fleet_unit.pk}) + +class SupplyListView(ListView): + model = PartRequest + template_name = 'core/supply_list.html' + context_object_name = 'requests' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + context['suppliers'] = Supplier.objects.all() + return context + +class SupplierCreateView(CreateView): + model = Supplier + form_class = SupplierForm + template_name = 'core/supplier_form.html' + success_url = reverse_lazy('supply_list') + +class SupplierUpdateView(UpdateView): + model = Supplier + form_class = SupplierForm + template_name = 'core/supplier_form.html' + success_url = reverse_lazy('supply_list') \ No newline at end of file diff --git a/media/qrcodes/qr-1.png b/media/qrcodes/qr-1.png new file mode 100644 index 0000000..b8d4e71 Binary files /dev/null and b/media/qrcodes/qr-1.png differ diff --git a/qrcodes/qr-1.png b/qrcodes/qr-1.png new file mode 100644 index 0000000..b8d4e71 Binary files /dev/null and b/qrcodes/qr-1.png differ diff --git a/staticfiles/css/custom.css b/staticfiles/css/custom.css index 108056f..424cf4a 100644 --- a/staticfiles/css/custom.css +++ b/staticfiles/css/custom.css @@ -1,21 +1,116 @@ - :root { - --bg-color-start: #6a11cb; - --bg-color-end: #2575fc; - --text-color: #ffffff; - --card-bg-color: rgba(255, 255, 255, 0.01); - --card-border-color: rgba(255, 255, 255, 0.1); + --bg-slate-900: #1e293b; + --text-blue-500: #3b82f6; + --primary: #3b82f6; + --secondary: #64748b; + --success: #10b981; + --danger: #ef4444; + --warning: #f59e0b; + --info: #06b6d4; } + body { - margin: 0; font-family: 'Inter', sans-serif; - background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end)); - color: var(--text-color); - display: flex; - justify-content: center; - align-items: center; - min-height: 100vh; - text-align: center; - overflow: hidden; - position: relative; + color: #334155; } + +h1, h2, h3, h4, h5, h6 { + font-family: 'Montserrat', sans-serif; + font-weight: 700; +} + +.bg-slate-900 { + background-color: var(--bg-slate-900) !important; +} + +.text-blue-500 { + color: var(--text-blue-500) !important; +} + +.tracking-wider { + letter-spacing: 0.05em; +} + +/* Card Styling */ +.card { + border: none; + border-radius: 12px; + transition: transform 0.2s ease, box-shadow 0.2s ease; +} + +.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; +} + +/* Stat Cards */ +.stat-card { + padding: 1.5rem; + display: flex; + align-items: center; +} + +.stat-icon { + width: 48px; + height: 48px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; + margin-right: 1rem; +} + +/* Badges */ +.badge { + padding: 0.5em 0.8em; + font-weight: 500; + border-radius: 6px; +} + +/* Hero Section */ +.hero-gradient { + background: linear-gradient(135deg, #1e293b 0%, #334155 100%); + color: white; + padding: 3rem 0; + border-radius: 16px; + margin-bottom: 2rem; +} + +/* Form Styling */ +.form-control, .form-select { + border-radius: 8px; + padding: 0.625rem 0.75rem; + border: 1px solid #e2e8f0; +} + +.form-control:focus, .form-select:focus { + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); + border-color: #3b82f6; +} + +.btn { + border-radius: 8px; + padding: 0.625rem 1.25rem; + font-weight: 500; +} + +.btn-primary { + background-color: var(--primary); + border-color: var(--primary); +} + +.btn-primary:hover { + background-color: #2563eb; + border-color: #2563eb; +} + +/* Mobile adjustments */ +@media (max-width: 768px) { + .stat-card { + padding: 1rem; + } + .hero-gradient { + padding: 2rem 1rem; + } +} \ No newline at end of file diff --git a/staticfiles/pasted-20260127-195120-f918f593.png b/staticfiles/pasted-20260127-195120-f918f593.png new file mode 100644 index 0000000..7d3ddee Binary files /dev/null and b/staticfiles/pasted-20260127-195120-f918f593.png differ diff --git a/staticfiles/pasted-20260127-200020-e0bae63b.png b/staticfiles/pasted-20260127-200020-e0bae63b.png new file mode 100644 index 0000000..1c8cf34 Binary files /dev/null and b/staticfiles/pasted-20260127-200020-e0bae63b.png differ diff --git a/staticfiles/pasted-20260127-200500-2ad7f024.png b/staticfiles/pasted-20260127-200500-2ad7f024.png new file mode 100644 index 0000000..27998c4 Binary files /dev/null and b/staticfiles/pasted-20260127-200500-2ad7f024.png differ