diff --git a/.gitignore b/.gitignore index ee01551..8e883cc 100644 --- a/.gitignore +++ b/.gitignore @@ -61,6 +61,8 @@ cover/ local_settings.py db.sqlite3 db.sqlite3-journal +# Media +media/ # Flask stuff: instance/ diff --git a/shiftflow/templates/shiftflow/item_detail copy.html b/shiftflow/templates/shiftflow/item_detail copy.html new file mode 100644 index 0000000..04d701e --- /dev/null +++ b/shiftflow/templates/shiftflow/item_detail copy.html @@ -0,0 +1,120 @@ +{% extends 'base.html' %} + +{% block title %}ShiftFlow | {{ item.drawing_name|default:"Б/ч" }}{% endblock %} + +{% block content %} +
+
+ +
+ +
+

+ + Деталь: {{ item.drawing_name|default:"Без чертежа" }} +

+ ID: {{ item.id }} +
+ +
+ {% csrf_token %} + +
+
+ +
{{ item.date|date:"d.m.Y" }}
+
+
+ +
{{ item.machine.name }}
+
+
+ +
№ {{ item.deal.number }}
+
+
+ +
{{ item.material.name }} (s{{ item.size_value|default:"-" }})
+
+
+ +
+
Файлы задания
+
+ {% if item.drawing_file %} + + Открыть DXF/STEP + + {% else %} + + {% endif %} + + {% if item.extra_drawing %} + + Открыть Чертеж PDF + + {% else %} + + {% endif %} +
+
+ +
+
Фактическое исполнение
+
+
+ + +
+ +
+ + +
+ +
+
+ + +
+
+
+
+ +
+ + +
+ + + + {% if form.errors %} +
+
Обнаружены ошибки:
+
    + {% for field, errors in form.errors.items %} + {% for error in errors %} +
  • {{ field|upper }}: {{ error }}
  • + {% endfor %} + {% endfor %} +
+
+ {% endif %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/shiftflow/templates/shiftflow/item_detail.html b/shiftflow/templates/shiftflow/item_detail.html new file mode 100644 index 0000000..da3a86c --- /dev/null +++ b/shiftflow/templates/shiftflow/item_detail.html @@ -0,0 +1,126 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+
+ +
+

{{ item.drawing_name|default:"Без названия" }}

+ Сделка № {{ item.deal.number }} +
+ +
+ {% csrf_token %} + +
+
+ Станок + {{ item.machine.name }} +
+
+ Материал + {{ item.material.name }} +
+
+ План + {{ item.quantity_plan }} шт. +
+
+ +
+ {% if item.drawing_file %}DXF{% endif %} + {% if item.extra_drawing %}PDF{% endif %} +
+ + + + {% if user_role in 'operator,master' %} + {% if item.status == 'work' %} +
+
Закрыть задание:
+
+ + +
+ +
+ + +
+
+ {% else %} +
Статус: {{ item.get_status_display }}. Сделано: {{ item.quantity_fact }} шт.
+ + {% endif %} + {% endif %} + + {% if user_role == 'admin' %} +
+
+ + +
+
+ + +
+
+ {% endif %} + + {% if user_role in 'admin,clerk' %} + {% if item.status == 'done' or item.quantity_fact > 0 %} +
+ + +
+ {% else %} +
Списание будет доступно после выполнения.
+ {% endif %} + {% if user_role == 'clerk' %}{% endif %} + {% endif %} + +
+ Назад + +
+
+
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/shiftflow/templates/shiftflow/items_list copy.html b/shiftflow/templates/shiftflow/items_list copy.html deleted file mode 100644 index 7ee18e9..0000000 --- a/shiftflow/templates/shiftflow/items_list copy.html +++ /dev/null @@ -1,65 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} - {% include 'shiftflow/partials/_filter.html' %} - - {% if not selected_machines %} -
- Станки не выбраны. Выберите станки в фильтре для отображения позиций. -
- {% else %} - - {% if in_work is not None %} -
В РАБОТЕ
-
- - - - - - - - - {% if user_role in 'admin,technologist,master,operator' %} - - {% endif %} - - - - {% for item in in_work %} - - - - - - - {% if user_role in 'admin,technologist,master,operator' %} - - {% endif %} - - {% empty %} - - {% endfor %} - -
СделкаДеталь / ЧертежМатериалПланФактДействия
{{ item.deal.number }}{{ item.drawing_name }} ({{ item.machine.name }}){{ item.material.name }}{{ item.quantity_plan }} - - - -
Активных заданий нет
-
- {% endif %} - - {% if backlog is not None %} -
НЕДОДЕЛЫ (ХВОСТЫ)
-
-
- {% endif %} - - {% if done_items is not None %} -
ЗАВЕРШЕНО
-
-
- {% endif %} - - {% endif %} -{% endblock %} \ No newline at end of file diff --git a/shiftflow/templates/shiftflow/items_list.html b/shiftflow/templates/shiftflow/items_list.html deleted file mode 100644 index 32c65da..0000000 --- a/shiftflow/templates/shiftflow/items_list.html +++ /dev/null @@ -1,38 +0,0 @@ -{% extends 'base.html' %} - -{% block content %} -
-
-

Реестр деталей

- {% if user_role == 'admin' or user_role == 'technologist' %} - - {% endif %} -
-
- - - - - - - - - - - {% for item in items %} - - - - - - - {% empty %} - - - - {% endfor %} - -
IDНаименованиеКол-воСтатус
{{ item.id }}{{ item.name }}{{ item.quantity }}{{ item.status }}
Деталей пока нет
-
-
-{% endblock %} \ No newline at end of file diff --git a/shiftflow/templates/shiftflow/landing.html b/shiftflow/templates/shiftflow/landing.html index 09c0970..6c92919 100644 --- a/shiftflow/templates/shiftflow/landing.html +++ b/shiftflow/templates/shiftflow/landing.html @@ -1,12 +1,14 @@ {% extends 'base.html' %} {% block content %} -
-

- ShiftFlow -

- - ВОЙТИ В СИСТЕМУ - +
+
{% endblock %} \ No newline at end of file diff --git a/shiftflow/templates/shiftflow/registry.html b/shiftflow/templates/shiftflow/registry.html new file mode 100644 index 0000000..44723a8 --- /dev/null +++ b/shiftflow/templates/shiftflow/registry.html @@ -0,0 +1,86 @@ +{% extends 'base.html' %} + +{% block content %} +
+
+

Реестр заданий

+
+ +
+
+ + + + + + + + + + + + + + + + + {% for item in items %} + + + + + + + + + + + + + {% empty %} + + {% endfor %} + +
ДатаСделкаСтанокНаименованиеГабаритыПлан / ФактМатериалФайлыСтатус
{{ item.date|date:"d.m.y" }}{{ item.deal.number }}{{ item.machine.name }}{{ item.drawing_name }}{{ item.size_value }} + {{ item.quantity_plan }} / + {{ item.quantity_fact }} + {{ item.material.name }} + {% if item.drawing_file %} + + + + {% endif %} + {% if item.extra_drawing %} + + + + {% endif %} + + {% if item.is_synced_1c %} + + {% else %} + + {% endif %} + + + {{ item.get_status_display }} + +
Заданий не найдено
+
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/shiftflow/urls.py b/shiftflow/urls.py index bbab240..c215e27 100644 --- a/shiftflow/urls.py +++ b/shiftflow/urls.py @@ -1,5 +1,5 @@ from django.urls import path -from .views import IndexView, RegistryView +from .views import IndexView, ItemUpdateView, RegistryView urlpatterns = [ # Главная страница (путь пустой) @@ -7,4 +7,5 @@ urlpatterns = [ # Реестр path('registry/', RegistryView.as_view(), name='registry'), + path('item//', ItemUpdateView.as_view(), name='item_detail'), ] \ No newline at end of file diff --git a/shiftflow/views copy.py b/shiftflow/views copy.py deleted file mode 100644 index 384b9b6..0000000 --- a/shiftflow/views copy.py +++ /dev/null @@ -1,72 +0,0 @@ -from django.shortcuts import render -from .models import Item, Machine -from django.utils import timezone -from datetime import datetime - -def items_list_view(request): - # Если не авторизован, просто отдаем пустой контекст для страницы входа - if not request.user.is_authenticated: - return render(request, 'shiftflow/items_list.html', {}) - - # ОПРЕДЕЛЕНИЕ РОЛИ (Ищем в группах Джанго или ставим суперюзера) - user_role = 'guest' - if request.user.is_superuser: - user_role = 'admin' - elif request.user.groups.filter(name='Технолог').exists(): - user_role = 'technologist' - elif request.user.groups.filter(name='Мастер').exists(): - user_role = 'master' - elif request.user.groups.filter(name='Оператор').exists(): - user_role = 'operator' - elif request.user.groups.filter(name='Учетчик').exists(): - user_role = 'clerk' - - # ФИЛЬТРЫ - all_machines = Machine.objects.all() - all_machine_ids = list(all_machines.values_list('id', flat=True)) - - # Проверяем, был ли нажат фильтр (есть ли параметр 'filtered' в URL) - is_filtered = 'filtered' in request.GET - - if is_filtered: - selected_machines = [int(x) for x in request.GET.getlist('m_ids')] - selected_statuses = request.GET.getlist('statuses') - start_date = request.GET.get('start_date') - end_date = request.GET.get('end_date') - else: - # Значения по умолчанию (при первом заходе или сбросе) - selected_machines = all_machine_ids - selected_statuses = ['work', 'partial'] # В работе и недоделы - start_date = timezone.now().strftime('%Y-%m-%d') - end_date = timezone.now().strftime('%Y-%m-%d') - - # ВЫБОРКА ДАННЫХ - items = Item.objects.select_related('deal', 'material', 'machine') - - # Защита логики: если станки не выбраны — список пуст - if not selected_machines: - items = Item.objects.none() - else: - items = items.filter( - machine_id__in=selected_machines, - status__in=selected_statuses, - date__range=[start_date, end_date] - ) - - # Разбиваем по статусам для вывода в разные таблицы (если они выбраны в фильтре) - in_work = items.filter(status='work') if 'work' in selected_statuses else None - backlog = items.filter(status='partial') if 'partial' in selected_statuses else None - done_items = items.filter(status='done') if 'done' in selected_statuses else None - - context = { - 'user_role': user_role, - 'machines': all_machines, - 'selected_machines': selected_machines, - 'selected_statuses': selected_statuses, - 'start_date': start_date, - 'end_date': end_date, - 'in_work': in_work, - 'backlog': backlog, - 'done_items': done_items, - } - return render(request, 'shiftflow/items_list.html', context) \ No newline at end of file diff --git a/shiftflow/views.py b/shiftflow/views.py index 3fa9b78..4731e9b 100644 --- a/shiftflow/views.py +++ b/shiftflow/views.py @@ -1,5 +1,6 @@ from django.shortcuts import redirect -from django.views.generic import TemplateView, ListView +from django.urls import reverse_lazy +from django.views.generic import TemplateView, ListView, UpdateView from django.contrib.auth.mixins import LoginRequiredMixin from .models import Item # Проверь, как точно называется твоя модель деталей/заказов @@ -17,16 +18,40 @@ class IndexView(TemplateView): # Класс реестра деталей (защищен LoginRequiredMixin) class RegistryView(LoginRequiredMixin, ListView): model = Item - template_name = 'shiftflow/items_list.html' + template_name = 'shiftflow/registry.html' context_object_name = 'items' def get_queryset(self): # Позже здесь добавим: .filter(machine__in=request.user.profile.machines.all()) - return Item.objects.all().order_by('-id') + # Сортируем: сначала статус (по алфавиту или логике choices), + # затем по дате (свежие сверху), по станку и по номеру сделки + return Item.objects.all().order_by('status', '-date', 'machine__name', 'deal__number') def get_context_data(self, **kwargs): context = super().get_context_data(**kwargs) # Передаем роль в шаблон, чтобы скрывать/показывать кнопки if hasattr(self.request.user, 'profile'): context['user_role'] = self.request.user.profile.role - return context \ No newline at end of file + return context + +# Вьюха детального вида и редактирования +class ItemUpdateView(LoginRequiredMixin, UpdateView): + model = Item + template_name = 'shiftflow/item_detail.html' + # Перечисляем поля, которые можно редактировать (укажи нужные) + fields = [ + 'drawing_name', 'machine', 'quantity_plan', 'quantity_fact', + 'material', 'size_value', 'status', 'is_synced_1c', 'extra_drawing' + ] + context_object_name = 'item' + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + # Обязательно добавляем роль в контекст этого шаблона! + if hasattr(self.request.user, 'profile'): + context['user_role'] = self.request.user.profile.role + return context + + def get_success_url(self): + # После сохранения возвращаемся в реестр + return reverse_lazy('registry') \ No newline at end of file diff --git a/static/css/style.css b/static/css/style.css index f763d1c..540a4fa 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,38 +1,105 @@ -/* Акцентные цвета для темной темы */ -[data-bs-theme="dark"] { - --bs-body-bg: #121212; - --bs-body-color: #e9ecef; - --bs-accent: #ffc107; /* Тот самый желтый */ +/* --- ГЛОБАЛЬНЫЕ НАСТРОЙКИ --- */ + +body { + display: flex; + flex-direction: column; + min-height: 100vh; + /* Убрали общее центрирование, чтобы реестр был сверху */ + justify-content: flex-start; } -[data-bs-theme="dark"] .table-custom-header { +/* Навбар и Футер: жестко фиксируем цвет для обеих тем */ +.navbar, .footer-custom { + /* Темный графит, который хорошо смотрится и там, и там */ + background-color: #2c3034 !important; + border-bottom: 1px solid #3d4246 !important; + border-top: 1px solid #3d4246 !important; /* Для футера */ +} + +/* Принудительно светлый текст для футера и навбара */ +.navbar .nav-link, +.navbar .navbar-brand, +.footer-custom span, +.footer-custom strong { + color: #e9ecef !important; +} + +/* Состояние активной ссылки в меню */ +.nav-link.active { + color: var(--bs-accent) !important; + border-bottom: 2px solid var(--bs-accent); +} + +/* Цвет ссылок в темном навбаре, чтобы не сливались */ +.navbar .nav-link, .navbar .navbar-brand, .navbar .text-reset { + color: #e9ecef !important; +} + +/* --- РЕЕСТР --- */ + +/* Делаем строку таблицы визуально кликабельной */ +.clickable-row { + cursor: pointer; + transition: background-color 0.2s ease; +} + +/* Подсветка при наведении */ +.clickable-row:hover { + background-color: rgba(255, 193, 7, 0.05) !important; /* Легкий отсвет нашего акцента */ +} + +/* --- ТЕМЫ --- */ + +[data-bs-theme="dark"] { + --bs-body-bg: #121212; /* Глубокий черный фон */ + --bs-body-color: #e9ecef; /* Светло-серый текст */ + --bs-accent: #ffc107; /* Желтый акцент (Amber) */ +} + +[data-bs-theme="light"] { + --bs-body-bg: #f8f9fa; /* Почти белый фон */ + --bs-body-color: #212529; /* Темный текст */ + --bs-accent: #0d6efd; /* Синий акцент для светлой темы */ +} + +/* --- ТАБЛИЦА И КАРТОЧКИ --- */ + +/* Заголовок таблицы: всегда темный с акцентным текстом */ +.table-custom-header { background-color: #1e1e1e !important; color: var(--bs-accent) !important; + font-size: 0.9rem; + text-transform: uppercase; /* Все буквы заглавные */ } -/* Акцентные цвета для светлой темы */ -[data-bs-theme="light"] { - --bs-body-bg: #e2e2e2; - --bs-body-color: #212529; - --bs-accent: #0f5132; /* Темно-зеленый */ +/* Фикс для таблиц в светлой теме */ +[data-bs-theme="light"] .table { + --bs-table-bg: #ffffff; + --bs-table-color: #212529; + --bs-table-hover-bg: #f1f3f5; } -/* Общие классы */ -.text-accent { - color: var(--bs-accent) !important; -} +/* --- ВСПОМОГАТЕЛЬНЫЕ КЛАССЫ --- */ +/* Текст акцентного цвета */ +.text-accent { color: var(--bs-accent) !important; } + +/* Кнопка с контуром акцентного цвета */ .btn-outline-accent { color: var(--bs-accent) !important; border-color: var(--bs-accent) !important; } +/* Состояние кнопки при наведении */ .btn-outline-accent:hover { background-color: var(--bs-accent) !important; - color: #000 !important; + color: #000 !important; /* Текст становится черным для контраста */ } -/* Фикс для навигации */ -.nav-link.active { - border-bottom: 2px solid var(--bs-accent); +/* Специальный класс для центрирования окна логина (вернем его только там) */ +.flex-center-center { + display: flex; + flex-grow: 1; + align-items: center; /* Центр по вертикали */ + justify-content: center; /* Центр по горизонтали */ } \ No newline at end of file diff --git a/templates/base.html b/templates/base.html index 8a47f62..aad3d88 100644 --- a/templates/base.html +++ b/templates/base.html @@ -15,7 +15,7 @@ {% include 'components/_navbar.html' %} {% endif %} -
+
{% block content %}{% endblock %}
diff --git a/templates/components/_footer.html b/templates/components/_footer.html index 7a3912f..5e6a20c 100644 --- a/templates/components/_footer.html +++ b/templates/components/_footer.html @@ -1,7 +1,8 @@ -