From e29509e7f23f679a758560b0a6482a2607afe727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9A=D0=BE=D1=88=D0=BA=D0=B8=D0=BD=20=D0=A1=D0=B5=D1=80?= =?UTF-8?q?=D0=B3=D0=B5=D0=B9?= Date: Tue, 16 Dec 2025 16:10:44 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=81=D1=82=D0=B0=D1=82=D0=B8=D1=81=D1=82=D0=B8=D0=BA=D1=83.?= =?UTF-8?q?=20=D0=9F=D0=BE=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B7=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=B8=D0=B5=20lyngsa?= =?UTF-8?q?t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dbapp/lyngsatapp/views.py | 26 +- ...032_alter_sourcerequest_status_and_more.py | 28 +++ dbapp/mainapp/models/requests.py | 5 + .../templates/mainapp/kubsat_tabs.html | 18 +- .../templates/mainapp/source_list.html | 36 +-- .../mainapp/templates/mainapp/statistics.html | 224 +++++++++--------- dbapp/mainapp/views/source.py | 6 +- dbapp/mainapp/views/statistics.py | 133 ++++++++--- 8 files changed, 271 insertions(+), 205 deletions(-) create mode 100644 dbapp/mainapp/migrations/0032_alter_sourcerequest_status_and_more.py diff --git a/dbapp/lyngsatapp/views.py b/dbapp/lyngsatapp/views.py index 10d9a29..b06a625 100644 --- a/dbapp/lyngsatapp/views.py +++ b/dbapp/lyngsatapp/views.py @@ -148,17 +148,21 @@ class LyngSatListView(LoginRequiredMixin, ListView): # Action buttons HTML for toolbar component from django.urls import reverse - action_buttons_html = f''' - - Добавить данные - - - Привязать - - - Отвязать - - ''' + from mainapp.permissions import has_permission + + action_buttons_html = '' + if has_permission(self.request.user, 'lyngsat_parse'): + action_buttons_html = f''' + + Добавить данные + + + Привязать + + + Отвязать + + ''' context['action_buttons_html'] = action_buttons_html # Build filter HTML list for filter_panel component diff --git a/dbapp/mainapp/migrations/0032_alter_sourcerequest_status_and_more.py b/dbapp/mainapp/migrations/0032_alter_sourcerequest_status_and_more.py new file mode 100644 index 0000000..d2df3bd --- /dev/null +++ b/dbapp/mainapp/migrations/0032_alter_sourcerequest_status_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.2.7 on 2025-12-16 12:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('mainapp', '0031_add_unique_date_location_constraint'), + ] + + operations = [ + migrations.AlterField( + model_name='sourcerequest', + name='status', + field=models.CharField(choices=[('planned', 'Запланировано'), ('canceled_gso', 'Отменено ГСО'), ('canceled_kub', 'Отменено МКА'), ('error_gso', 'Ошибка ГСО'), ('error_kub', 'Ошибка МКА'), ('wait_exec', 'Ожидают проведения'), ('suggested', 'Предложено'), ('gso_fault', 'Не проведены по вине ГСО'), ('conducted', 'Проведён'), ('successful', 'Успешно'), ('no_correlation', 'Нет корреляции'), ('no_signal', 'Нет сигнала в спектре'), ('unsuccessful', 'Неуспешно'), ('downloading', 'Скачивание'), ('processing', 'Обработка'), ('result_received', 'Результат получен')], db_index=True, default='planned', help_text='Текущий статус заявки', max_length=20, verbose_name='Статус'), + ), + migrations.AlterField( + model_name='sourcerequeststatushistory', + name='new_status', + field=models.CharField(choices=[('planned', 'Запланировано'), ('canceled_gso', 'Отменено ГСО'), ('canceled_kub', 'Отменено МКА'), ('error_gso', 'Ошибка ГСО'), ('error_kub', 'Ошибка МКА'), ('wait_exec', 'Ожидают проведения'), ('suggested', 'Предложено'), ('gso_fault', 'Не проведены по вине ГСО'), ('conducted', 'Проведён'), ('successful', 'Успешно'), ('no_correlation', 'Нет корреляции'), ('no_signal', 'Нет сигнала в спектре'), ('unsuccessful', 'Неуспешно'), ('downloading', 'Скачивание'), ('processing', 'Обработка'), ('result_received', 'Результат получен')], help_text='Статус после изменения', max_length=20, verbose_name='Новый статус'), + ), + migrations.AlterField( + model_name='sourcerequeststatushistory', + name='old_status', + field=models.CharField(choices=[('planned', 'Запланировано'), ('canceled_gso', 'Отменено ГСО'), ('canceled_kub', 'Отменено МКА'), ('error_gso', 'Ошибка ГСО'), ('error_kub', 'Ошибка МКА'), ('wait_exec', 'Ожидают проведения'), ('suggested', 'Предложено'), ('gso_fault', 'Не проведены по вине ГСО'), ('conducted', 'Проведён'), ('successful', 'Успешно'), ('no_correlation', 'Нет корреляции'), ('no_signal', 'Нет сигнала в спектре'), ('unsuccessful', 'Неуспешно'), ('downloading', 'Скачивание'), ('processing', 'Обработка'), ('result_received', 'Результат получен')], help_text='Статус до изменения', max_length=20, verbose_name='Старый статус'), + ), + ] diff --git a/dbapp/mainapp/models/requests.py b/dbapp/mainapp/models/requests.py index c4f349a..53e1d04 100644 --- a/dbapp/mainapp/models/requests.py +++ b/dbapp/mainapp/models/requests.py @@ -16,6 +16,11 @@ class SourceRequest(models.Model): ('planned', 'Запланировано'), ('canceled_gso', 'Отменено ГСО'), ('canceled_kub', 'Отменено МКА'), + ('error_gso', 'Ошибка ГСО'), + ('error_kub', 'Ошибка МКА'), + ('wait_exec', 'Ожидают проведения'), + ('suggested', 'Предложено'), + ('gso_fault', 'Не проведены по вине ГСО'), ('conducted', 'Проведён'), ('successful', 'Успешно'), ('no_correlation', 'Нет корреляции'), diff --git a/dbapp/mainapp/templates/mainapp/kubsat_tabs.html b/dbapp/mainapp/templates/mainapp/kubsat_tabs.html index efe5a51..c56b765 100644 --- a/dbapp/mainapp/templates/mainapp/kubsat_tabs.html +++ b/dbapp/mainapp/templates/mainapp/kubsat_tabs.html @@ -81,23 +81,17 @@
diff --git a/dbapp/mainapp/templates/mainapp/source_list.html b/dbapp/mainapp/templates/mainapp/source_list.html index 65a397c..a4e069c 100644 --- a/dbapp/mainapp/templates/mainapp/source_list.html +++ b/dbapp/mainapp/templates/mainapp/source_list.html @@ -377,15 +377,9 @@ onclick="selectAllOptions('request_status', false)">Снять @@ -393,9 +387,9 @@
@@ -2480,23 +2474,17 @@ function showTransponderModal(transponderId) {
diff --git a/dbapp/mainapp/templates/mainapp/statistics.html b/dbapp/mainapp/templates/mainapp/statistics.html index 271ea66..9adf330 100644 --- a/dbapp/mainapp/templates/mainapp/statistics.html +++ b/dbapp/mainapp/templates/mainapp/statistics.html @@ -558,86 +558,103 @@
-
-
-
- Кубсаты + +
+
+
+ Запланировано + {{ extended_stats.kubsat.planned_count }}
-
-
-
-
{{ extended_stats.kubsat.planned_count }}
-
- Запланировано -
-
-
-
-
-
{{ extended_stats.kubsat.conducted_count }}
-
- Проведено -
-
-
-
-
-
{{ extended_stats.kubsat.canceled_gso_count }}
-
- Отменено ГСО -
-
-
-
-
-
{{ extended_stats.kubsat.canceled_kub_count }}
-
- Отменено МКА -
-
-
+
+ + Проведено: + + {{ extended_stats.kubsat.conducted_count }}
- - - {% if extended_stats.kubsat.planned_count > 0 %} -
-
- Распределение статусов - Всего: {{ extended_stats.kubsat.planned_count }} -
-
- {% with total=extended_stats.kubsat.planned_count conducted=extended_stats.kubsat.conducted_count canceled_gso=extended_stats.kubsat.canceled_gso_count canceled_kub=extended_stats.kubsat.canceled_kub_count %} - {% if conducted > 0 %} -
- {{ conducted }} -
- {% endif %} - {% if canceled_gso > 0 %} -
- {{ canceled_gso }} -
- {% endif %} - {% if canceled_kub > 0 %} -
- {{ canceled_kub }} -
- {% endif %} - {% endwith %} -
-
- Проведено - Отменено ГСО - Отменено МКА -
+
+ + Отменено (ГСО): + + {{ extended_stats.kubsat.canceled_gso_count }} +
+
+ + Отменено (МКА): + + {{ extended_stats.kubsat.canceled_kub_count }} +
+
+ + Ожидают проведения: + + {{ extended_stats.kubsat.wait_exec_count }} +
+
+ + Не проведены (вина ГСО): + + {{ extended_stats.kubsat.gso_fault_count }} +
+
+
+
+ + +
+
+
+ Проведено + {{ extended_stats.kubsat.total_conducted_count }} +
+
+
+ + Результат получен: + + {{ extended_stats.kubsat.result_received_count }} +
+
+ + Обработка: + + {{ extended_stats.kubsat.processing_count }} +
+
+ + Нет корреляции: + + {{ extended_stats.kubsat.no_correlation_count }} +
+
+ + Ошибка ГСО: + + {{ extended_stats.kubsat.error_gso_count }} +
+
+ + Ошибка МКА: + + {{ extended_stats.kubsat.error_kub_count }} +
+
+
+
+
+ + +
+
+
+
+
+ + + Предложено: + + {{ extended_stats.kubsat.suggested_count }}
- {% endif %}
@@ -1631,21 +1648,31 @@ document.addEventListener('DOMContentLoaded', function() { document.getElementById('dv-new-coords').textContent = data.extended_stats.dv.new_coords; document.getElementById('dv-transfer-delta').textContent = data.extended_stats.dv.transfer_delta + ' МГц'; - // Update Kubsat statistics + // Update Kubsat statistics - Запланировано document.getElementById('kubsat-planned').textContent = data.extended_stats.kubsat.planned_count; document.getElementById('kubsat-conducted').textContent = data.extended_stats.kubsat.conducted_count; document.getElementById('kubsat-canceled-gso').textContent = data.extended_stats.kubsat.canceled_gso_count; document.getElementById('kubsat-canceled-kub').textContent = data.extended_stats.kubsat.canceled_kub_count; + document.getElementById('kubsat-wait-exec').textContent = data.extended_stats.kubsat.wait_exec_count; + document.getElementById('kubsat-gso-fault').textContent = data.extended_stats.kubsat.gso_fault_count; - // Update progress bar if exists - updateKubsatProgressBar(data.extended_stats.kubsat); + // Update Kubsat statistics - Проведено + document.getElementById('kubsat-total-conducted').textContent = data.extended_stats.kubsat.total_conducted_count; + document.getElementById('kubsat-result-received').textContent = data.extended_stats.kubsat.result_received_count; + document.getElementById('kubsat-processing').textContent = data.extended_stats.kubsat.processing_count; + document.getElementById('kubsat-no-correlation').textContent = data.extended_stats.kubsat.no_correlation_count; + document.getElementById('kubsat-error-gso').textContent = data.extended_stats.kubsat.error_gso_count; + document.getElementById('kubsat-error-kub').textContent = data.extended_stats.kubsat.error_kub_count; + + // Update Kubsat statistics - Предложено + document.getElementById('kubsat-suggested').textContent = data.extended_stats.kubsat.suggested_count; // Update period info updatePeriodInfo(data.date_from, data.date_to); }) .catch(error => { console.error('Error loading extended stats:', error); - alert('Ошибка загрузки статистики'); + // alert('Ошибка загрузки статистики'); }) .finally(() => { // Hide loading indicator @@ -1654,43 +1681,6 @@ document.addEventListener('DOMContentLoaded', function() { }); } - // Update Kubsat progress bar - function updateKubsatProgressBar(kubsat) { - const progressContainer = document.querySelector('.progress'); - if (!progressContainer) return; - - const total = kubsat.planned_count; - if (total === 0) { - progressContainer.innerHTML = ''; - return; - } - - let html = ''; - - if (kubsat.conducted_count > 0) { - const pct = Math.round((kubsat.conducted_count / total) * 100); - html += `
${kubsat.conducted_count}
`; - } - - if (kubsat.canceled_gso_count > 0) { - const pct = Math.round((kubsat.canceled_gso_count / total) * 100); - html += `
${kubsat.canceled_gso_count}
`; - } - - if (kubsat.canceled_kub_count > 0) { - const pct = Math.round((kubsat.canceled_kub_count / total) * 100); - html += `
${kubsat.canceled_kub_count}
`; - } - - progressContainer.innerHTML = html; - - // Update total count - const totalLabel = progressContainer.parentElement.querySelector('.text-muted:last-of-type'); - if (totalLabel) { - totalLabel.textContent = 'Всего: ' + total; - } - } - // Update period info display function updatePeriodInfo(dateFrom, dateTo) { const periodAlert = document.querySelector('#extendedStatsModal .alert-light'); diff --git a/dbapp/mainapp/views/source.py b/dbapp/mainapp/views/source.py index 26b0f9c..c4676d8 100644 --- a/dbapp/mainapp/views/source.py +++ b/dbapp/mainapp/views/source.py @@ -15,7 +15,7 @@ from django.urls import reverse from django.views import View from ..forms import SourceForm -from ..models import Source, Satellite +from ..models import Source, Satellite, SourceRequest from ..utils import format_coords_display, parse_pagination_params from ..permissions import PermissionRequiredMixin, permission_required @@ -419,7 +419,6 @@ class SourceListView(LoginRequiredMixin, View): # Filter by source requests if has_requests == "1": # Has requests - apply subfilters - from ..models import SourceRequest from django.db.models import Exists, OuterRef # Build subquery for filtering requests @@ -812,6 +811,9 @@ class SourceListView(LoginRequiredMixin, View): 'object_infos': object_infos, 'polygon_coords': json.dumps(polygon_coords) if polygon_coords else None, 'full_width_page': True, + # Status and priority choices from model + 'status_choices': SourceRequest.STATUS_CHOICES, + 'priority_choices': SourceRequest.PRIORITY_CHOICES, } return render(request, "mainapp/source_list.html", context) diff --git a/dbapp/mainapp/views/statistics.py b/dbapp/mainapp/views/statistics.py index 1961fa5..7d628e7 100644 --- a/dbapp/mainapp/views/statistics.py +++ b/dbapp/mainapp/views/statistics.py @@ -313,37 +313,39 @@ class StatisticsView(PermissionRequiredMixin, TemplateView): Получает статистику по Кубсатам из SourceRequest. Возвращает: - - planned_count: количество запланированных сеансов - - conducted_count: количество проведённых - - canceled_gso_count: количество отменённых ГСО - - canceled_kub_count: количество отменённых МКА + 1. Запланировано (по истории статуса 'planned'): + - planned_count: всего запланировано + - conducted_count: проведено + - canceled_gso_count: отменено ГСО + - canceled_kub_count: отменено МКА + - wait_exec_count: ожидают проведения + - gso_fault_count: не проведены по вине ГСО + + 2. Проведено (по истории статуса 'conducted'): + - total_conducted_count: всего проведено + - result_received_count: результат получен + - processing_count: обработка + - no_correlation_count: нет корреляции + - error_gso_count: ошибка ГСО + - error_kub_count: ошибка МКА + + 3. Предложено: + - suggested_count: количество предложенных """ - # Базовый queryset для заявок - requests_qs = SourceRequest.objects.all() - - # Фильтруем по дате создания или planned_at - if date_from: - requests_qs = requests_qs.filter( - Q(created_at__date__gte=date_from) | Q(planned_at__date__gte=date_from) - ) - if date_to: - requests_qs = requests_qs.filter( - Q(created_at__date__lte=date_to) | Q(planned_at__date__lte=date_to) - ) - - # Получаем ID заявок, у которых в истории был статус 'planned' - # Это заявки, которые были запланированы в выбранном периоде - history_qs = SourceRequestStatusHistory.objects.filter( + # === 1. ЗАПЛАНИРОВАНО === + # Получаем ID заявок, у которых в истории был статус 'planned' в периоде + history_planned_qs = SourceRequestStatusHistory.objects.filter( new_status='planned' ) if date_from: - history_qs = history_qs.filter(changed_at__date__gte=date_from) + history_planned_qs = history_planned_qs.filter(changed_at__date__gte=date_from) if date_to: - history_qs = history_qs.filter(changed_at__date__lte=date_to) + history_planned_qs = history_planned_qs.filter(changed_at__date__lte=date_to) - planned_request_ids = set(history_qs.values_list('source_request_id', flat=True)) + planned_request_ids = set(history_planned_qs.values_list('source_request_id', flat=True)) # Также добавляем заявки, которые были созданы со статусом 'planned' в периоде + # (для случаев, когда заявка создана сразу с этим статусом без истории) created_planned_qs = SourceRequest.objects.filter(status='planned') if date_from: created_planned_qs = created_planned_qs.filter(created_at__date__gte=date_from) @@ -354,35 +356,88 @@ class StatisticsView(PermissionRequiredMixin, TemplateView): planned_count = len(planned_request_ids) - # Считаем статусы из истории для запланированных заявок + # Считаем текущие статусы для запланированных заявок conducted_count = 0 canceled_gso_count = 0 canceled_kub_count = 0 + wait_exec_count = 0 + gso_fault_count = 0 if planned_request_ids: - # Получаем историю статусов для запланированных заявок - status_history = SourceRequestStatusHistory.objects.filter( - source_request_id__in=planned_request_ids - ) - if date_from: - status_history = status_history.filter(changed_at__date__gte=date_from) - if date_to: - status_history = status_history.filter(changed_at__date__lte=date_to) + # Получаем текущие статусы запланированных заявок + planned_requests = SourceRequest.objects.filter(id__in=planned_request_ids) - # Считаем уникальные заявки по каждому статусу - conducted_ids = set(status_history.filter(new_status='conducted').values_list('source_request_id', flat=True)) - canceled_gso_ids = set(status_history.filter(new_status='canceled_gso').values_list('source_request_id', flat=True)) - canceled_kub_ids = set(status_history.filter(new_status='canceled_kub').values_list('source_request_id', flat=True)) + conducted_count = planned_requests.filter(status='conducted').count() + canceled_gso_count = planned_requests.filter(status='canceled_gso').count() + canceled_kub_count = planned_requests.filter(status='canceled_kub').count() + wait_exec_count = planned_requests.filter(status='wait_exec').count() + gso_fault_count = planned_requests.filter(status='gso_fault').count() + + # === 2. ПРОВЕДЕНО === + # Получаем ID заявок, у которых в истории был статус 'conducted' в периоде + history_conducted_qs = SourceRequestStatusHistory.objects.filter( + new_status='conducted' + ) + if date_from: + history_conducted_qs = history_conducted_qs.filter(changed_at__date__gte=date_from) + if date_to: + history_conducted_qs = history_conducted_qs.filter(changed_at__date__lte=date_to) + + conducted_request_ids = set(history_conducted_qs.values_list('source_request_id', flat=True)) + + # Также добавляем заявки с текущим статусом 'conducted', созданные в периоде + created_conducted_qs = SourceRequest.objects.filter(status='conducted') + if date_from: + created_conducted_qs = created_conducted_qs.filter(created_at__date__gte=date_from) + if date_to: + created_conducted_qs = created_conducted_qs.filter(created_at__date__lte=date_to) + + conducted_request_ids.update(created_conducted_qs.values_list('id', flat=True)) + + total_conducted_count = len(conducted_request_ids) + + # Считаем текущие статусы для проведённых заявок + result_received_count = 0 + processing_count = 0 + no_correlation_count = 0 + error_gso_count = 0 + error_kub_count = 0 + + if conducted_request_ids: + conducted_requests = SourceRequest.objects.filter(id__in=conducted_request_ids) - conducted_count = len(conducted_ids) - canceled_gso_count = len(canceled_gso_ids) - canceled_kub_count = len(canceled_kub_ids) + result_received_count = conducted_requests.filter(status='result_received').count() + processing_count = conducted_requests.filter(status='processing').count() + no_correlation_count = conducted_requests.filter(status='no_correlation').count() + error_gso_count = conducted_requests.filter(status='error_gso').count() + error_kub_count = conducted_requests.filter(status='error_kub').count() + + # === 3. ПРЕДЛОЖЕНО === + suggested_qs = SourceRequest.objects.filter(status='suggested') + if date_from: + suggested_qs = suggested_qs.filter(created_at__date__gte=date_from) + if date_to: + suggested_qs = suggested_qs.filter(created_at__date__lte=date_to) + + suggested_count = suggested_qs.count() return { + # Запланировано 'planned_count': planned_count, 'conducted_count': conducted_count, 'canceled_gso_count': canceled_gso_count, 'canceled_kub_count': canceled_kub_count, + 'wait_exec_count': wait_exec_count, + 'gso_fault_count': gso_fault_count, + # Проведено + 'total_conducted_count': total_conducted_count, + 'result_received_count': result_received_count, + 'processing_count': processing_count, + 'no_correlation_count': no_correlation_count, + 'error_gso_count': error_gso_count, + 'error_kub_count': error_kub_count, + # Предложено + 'suggested_count': suggested_count, } def get_extended_statistics(self, date_from, date_to):