Доделал статистику. Поправил разрешение lyngsat

This commit is contained in:
2025-12-16 16:10:44 +03:00
parent df5719fb8f
commit e29509e7f2
8 changed files with 271 additions and 205 deletions

View File

@@ -148,17 +148,21 @@ class LyngSatListView(LoginRequiredMixin, ListView):
# Action buttons HTML for toolbar component # Action buttons HTML for toolbar component
from django.urls import reverse from django.urls import reverse
action_buttons_html = f''' from mainapp.permissions import has_permission
<a href="{reverse('mainapp:fill_lyngsat_data')}" class="btn btn-secondary btn-sm" title="Заполнить данные Lyngsat">
<i class="bi bi-cloud-download"></i> Добавить данные action_buttons_html = ''
</a> if has_permission(self.request.user, 'lyngsat_parse'):
<a href="{reverse('mainapp:link_lyngsat')}" class="btn btn-primary btn-sm" title="Привязать источники LyngSat"> action_buttons_html = f'''
<i class="bi bi-link-45deg"></i> Привязать <a href="{reverse('mainapp:fill_lyngsat_data')}" class="btn btn-secondary btn-sm" title="Заполнить данные Lyngsat">
</a> <i class="bi bi-cloud-download"></i> Добавить данные
<a href="{reverse('mainapp:unlink_all_lyngsat')}" class="btn btn-warning btn-sm" title="Отвязать все источники LyngSat"> </a>
<i class="bi bi-x-circle"></i> Отвязать <a href="{reverse('mainapp:link_lyngsat')}" class="btn btn-primary btn-sm" title="Привязать источники LyngSat">
</a> <i class="bi bi-link-45deg"></i> Привязать
''' </a>
<a href="{reverse('mainapp:unlink_all_lyngsat')}" class="btn btn-warning btn-sm" title="Отвязать все источники LyngSat">
<i class="bi bi-x-circle"></i> Отвязать
</a>
'''
context['action_buttons_html'] = action_buttons_html context['action_buttons_html'] = action_buttons_html
# Build filter HTML list for filter_panel component # Build filter HTML list for filter_panel component

View File

@@ -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='Старый статус'),
),
]

View File

@@ -16,6 +16,11 @@ class SourceRequest(models.Model):
('planned', 'Запланировано'), ('planned', 'Запланировано'),
('canceled_gso', 'Отменено ГСО'), ('canceled_gso', 'Отменено ГСО'),
('canceled_kub', 'Отменено МКА'), ('canceled_kub', 'Отменено МКА'),
('error_gso', 'Ошибка ГСО'),
('error_kub', 'Ошибка МКА'),
('wait_exec', 'Ожидают проведения'),
('suggested', 'Предложено'),
('gso_fault', 'Не проведены по вине ГСО'),
('conducted', 'Проведён'), ('conducted', 'Проведён'),
('successful', 'Успешно'), ('successful', 'Успешно'),
('no_correlation', 'Нет корреляции'), ('no_correlation', 'Нет корреляции'),

View File

@@ -81,23 +81,17 @@
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3">
<label for="requestStatus" class="form-label">Статус</label> <label for="requestStatus" class="form-label">Статус</label>
<select class="form-select" id="requestStatus" name="status"> <select class="form-select" id="requestStatus" name="status">
<option value="planned">Запланировано</option> {% for value, label in status_choices %}
<option value="conducted">Проведён</option> <option value="{{ value }}">{{ label }}</option>
<option value="successful">Успешно</option> {% endfor %}
<option value="no_correlation">Нет корреляции</option>
<option value="no_signal">Нет сигнала в спектре</option>
<option value="unsuccessful">Неуспешно</option>
<option value="downloading">Скачивание</option>
<option value="processing">Обработка</option>
<option value="result_received">Результат получен</option>
</select> </select>
</div> </div>
<div class="col-md-3 mb-3"> <div class="col-md-3 mb-3">
<label for="requestPriority" class="form-label">Приоритет</label> <label for="requestPriority" class="form-label">Приоритет</label>
<select class="form-select" id="requestPriority" name="priority"> <select class="form-select" id="requestPriority" name="priority">
<option value="low">Низкий</option> {% for value, label in priority_choices %}
<option value="medium" selected>Средний</option> <option value="{{ value }}" {% if value == 'medium' %}selected{% endif %}>{{ label }}</option>
<option value="high">Высокий</option> {% endfor %}
</select> </select>
</div> </div>
</div> </div>

View File

@@ -377,15 +377,9 @@
onclick="selectAllOptions('request_status', false)">Снять</button> onclick="selectAllOptions('request_status', false)">Снять</button>
</div> </div>
<select name="request_status" class="form-select form-select-sm" multiple size="5"> <select name="request_status" class="form-select form-select-sm" multiple size="5">
<option value="planned" {% if 'planned' in selected_request_statuses %}selected{% endif %}>Запланировано</option> {% for value, label in status_choices %}
<option value="conducted" {% if 'conducted' in selected_request_statuses %}selected{% endif %}>Проведён</option> <option value="{{ value }}" {% if value in selected_request_statuses %}selected{% endif %}>{{ label }}</option>
<option value="successful" {% if 'successful' in selected_request_statuses %}selected{% endif %}>Успешно</option> {% endfor %}
<option value="no_correlation" {% if 'no_correlation' in selected_request_statuses %}selected{% endif %}>Нет корреляции</option>
<option value="no_signal" {% if 'no_signal' in selected_request_statuses %}selected{% endif %}>Нет сигнала в спектре</option>
<option value="unsuccessful" {% if 'unsuccessful' in selected_request_statuses %}selected{% endif %}>Неуспешно</option>
<option value="downloading" {% if 'downloading' in selected_request_statuses %}selected{% endif %}>Скачивание</option>
<option value="processing" {% if 'processing' in selected_request_statuses %}selected{% endif %}>Обработка</option>
<option value="result_received" {% if 'result_received' in selected_request_statuses %}selected{% endif %}>Результат получен</option>
</select> </select>
</div> </div>
@@ -393,9 +387,9 @@
<div class="mb-2"> <div class="mb-2">
<label class="form-label small">Приоритет:</label> <label class="form-label small">Приоритет:</label>
<select name="request_priority" class="form-select form-select-sm" multiple size="3"> <select name="request_priority" class="form-select form-select-sm" multiple size="3">
<option value="low" {% if 'low' in selected_request_priorities %}selected{% endif %}>Низкий</option> {% for value, label in priority_choices %}
<option value="medium" {% if 'medium' in selected_request_priorities %}selected{% endif %}>Средний</option> <option value="{{ value }}" {% if value in selected_request_priorities %}selected{% endif %}>{{ label }}</option>
<option value="high" {% if 'high' in selected_request_priorities %}selected{% endif %}>Высокий</option> {% endfor %}
</select> </select>
</div> </div>
@@ -2480,23 +2474,17 @@ function showTransponderModal(transponderId) {
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="editRequestStatus" class="form-label">Статус</label> <label for="editRequestStatus" class="form-label">Статус</label>
<select class="form-select" id="editRequestStatus" name="status"> <select class="form-select" id="editRequestStatus" name="status">
<option value="planned">Запланировано</option> {% for value, label in status_choices %}
<option value="conducted">Проведён</option> <option value="{{ value }}">{{ label }}</option>
<option value="successful">Успешно</option> {% endfor %}
<option value="no_correlation">Нет корреляции</option>
<option value="no_signal">Нет сигнала в спектре</option>
<option value="unsuccessful">Неуспешно</option>
<option value="downloading">Скачивание</option>
<option value="processing">Обработка</option>
<option value="result_received">Результат получен</option>
</select> </select>
</div> </div>
<div class="col-md-6 mb-3"> <div class="col-md-6 mb-3">
<label for="editRequestPriority" class="form-label">Приоритет</label> <label for="editRequestPriority" class="form-label">Приоритет</label>
<select class="form-select" id="editRequestPriority" name="priority"> <select class="form-select" id="editRequestPriority" name="priority">
<option value="low">Низкий</option> {% for value, label in priority_choices %}
<option value="medium" selected>Средний</option> <option value="{{ value }}" {% if value == 'medium' %}selected{% endif %}>{{ label }}</option>
<option value="high">Высокий</option> {% endfor %}
</select> </select>
</div> </div>
</div> </div>

View File

@@ -558,86 +558,103 @@
<!-- Kubsat Statistics --> <!-- Kubsat Statistics -->
<div class="row"> <div class="row">
<div class="col-12"> <!-- 1. Запланировано -->
<div class="card border-secondary"> <div class="col-md-6 mb-4">
<div class="card-header bg-secondary text-white"> <div class="card h-100 border-primary">
<strong>Кубсаты</strong> <div class="card-header bg-primary text-white">
<strong><i class="bi bi-calendar-check"></i> Запланировано</strong>
<span class="badge bg-light text-primary float-end fs-6" id="kubsat-planned">{{ extended_stats.kubsat.planned_count }}</span>
</div> </div>
<div class="card-body"> <div class="card-body">
<div class="row text-center"> <div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
<div class="col-md-3 mb-3 mb-md-0"> <span class="text-muted">
<div class="p-3 bg-light rounded"> <i class="bi bi-check-circle text-success"></i> Проведено:
<div class="fs-3 fw-bold text-primary" id="kubsat-planned">{{ extended_stats.kubsat.planned_count }}</div> </span>
<div class="text-muted small"> <span class="badge bg-success" id="kubsat-conducted">{{ extended_stats.kubsat.conducted_count }}</span>
<i class="bi bi-calendar-check"></i> Запланировано
</div>
</div>
</div>
<div class="col-md-3 mb-3 mb-md-0">
<div class="p-3 bg-light rounded">
<div class="fs-3 fw-bold text-success" id="kubsat-conducted">{{ extended_stats.kubsat.conducted_count }}</div>
<div class="text-muted small">
<i class="bi bi-check-circle"></i> Проведено
</div>
</div>
</div>
<div class="col-md-3 mb-3 mb-md-0">
<div class="p-3 bg-light rounded">
<div class="fs-3 fw-bold text-danger" id="kubsat-canceled-gso">{{ extended_stats.kubsat.canceled_gso_count }}</div>
<div class="text-muted small">
<i class="bi bi-x-circle"></i> Отменено ГСО
</div>
</div>
</div>
<div class="col-md-3">
<div class="p-3 bg-light rounded">
<div class="fs-3 fw-bold text-warning" id="kubsat-canceled-kub">{{ extended_stats.kubsat.canceled_kub_count }}</div>
<div class="text-muted small">
<i class="bi bi-x-circle"></i> Отменено МКА
</div>
</div>
</div>
</div> </div>
<div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
<!-- Progress bar for Kubsat --> <span class="text-muted">
{% if extended_stats.kubsat.planned_count > 0 %} <i class="bi bi-x-circle text-danger"></i> Отменено (ГСО):
<div class="mt-4"> </span>
<div class="d-flex justify-content-between mb-1"> <span class="badge bg-danger" id="kubsat-canceled-gso">{{ extended_stats.kubsat.canceled_gso_count }}</span>
<small class="text-muted">Распределение статусов</small> </div>
<small class="text-muted">Всего: {{ extended_stats.kubsat.planned_count }}</small> <div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
</div> <span class="text-muted">
<div class="progress" style="height: 25px;"> <i class="bi bi-x-circle text-warning"></i> Отменено (МКА):
{% 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 %} </span>
{% if conducted > 0 %} <span class="badge bg-warning text-dark" id="kubsat-canceled-kub">{{ extended_stats.kubsat.canceled_kub_count }}</span>
<div class="progress-bar bg-success" role="progressbar" </div>
style="width: {% widthratio conducted total 100 %}%" <div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
title="Проведено: {{ conducted }}"> <span class="text-muted">
{{ conducted }} <i class="bi bi-hourglass-split text-info"></i> Ожидают проведения:
</div> </span>
{% endif %} <span class="badge bg-info" id="kubsat-wait-exec">{{ extended_stats.kubsat.wait_exec_count }}</span>
{% if canceled_gso > 0 %} </div>
<div class="progress-bar bg-danger" role="progressbar" <div class="d-flex justify-content-between align-items-center">
style="width: {% widthratio canceled_gso total 100 %}%" <span class="text-muted">
title="Отменено ГСО: {{ canceled_gso }}"> <i class="bi bi-exclamation-triangle text-secondary"></i> Не проведены (вина ГСО):
{{ canceled_gso }} </span>
</div> <span class="badge bg-secondary" id="kubsat-gso-fault">{{ extended_stats.kubsat.gso_fault_count }}</span>
{% endif %} </div>
{% if canceled_kub > 0 %} </div>
<div class="progress-bar bg-warning text-dark" role="progressbar" </div>
style="width: {% widthratio canceled_kub total 100 %}%" </div>
title="Отменено МКА: {{ canceled_kub }}">
{{ canceled_kub }} <!-- 2. Проведено -->
</div> <div class="col-md-6 mb-4">
{% endif %} <div class="card h-100 border-success">
{% endwith %} <div class="card-header bg-success text-white">
</div> <strong><i class="bi bi-check2-circle"></i> Проведено</strong>
<div class="d-flex justify-content-center mt-2"> <span class="badge bg-light text-success float-end fs-6" id="kubsat-total-conducted">{{ extended_stats.kubsat.total_conducted_count }}</span>
<small class="me-3"><span class="badge bg-success"></span> Проведено</small> </div>
<small class="me-3"><span class="badge bg-danger"></span> Отменено ГСО</small> <div class="card-body">
<small><span class="badge bg-warning text-dark"></span> Отменено МКА</small> <div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
</div> <span class="text-muted">
<i class="bi bi-check-lg text-success"></i> Результат получен:
</span>
<span class="badge bg-success" id="kubsat-result-received">{{ extended_stats.kubsat.result_received_count }}</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
<span class="text-muted">
<i class="bi bi-gear text-primary"></i> Обработка:
</span>
<span class="badge bg-primary" id="kubsat-processing">{{ extended_stats.kubsat.processing_count }}</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
<span class="text-muted">
<i class="bi bi-question-circle text-warning"></i> Нет корреляции:
</span>
<span class="badge bg-warning text-dark" id="kubsat-no-correlation">{{ extended_stats.kubsat.no_correlation_count }}</span>
</div>
<div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
<span class="text-muted">
<i class="bi bi-bug text-danger"></i> Ошибка ГСО:
</span>
<span class="badge bg-danger" id="kubsat-error-gso">{{ extended_stats.kubsat.error_gso_count }}</span>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted">
<i class="bi bi-bug text-secondary"></i> Ошибка МКА:
</span>
<span class="badge bg-secondary" id="kubsat-error-kub">{{ extended_stats.kubsat.error_kub_count }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 3. Предложено -->
<div class="row">
<div class="col-12">
<div class="card border-info">
<div class="card-body py-3">
<div class="d-flex justify-content-between align-items-center">
<span>
<i class="bi bi-lightbulb text-info fs-5"></i>
<strong class="ms-2">Предложено:</strong>
</span>
<span class="badge bg-info fs-5" id="kubsat-suggested">{{ extended_stats.kubsat.suggested_count }}</span>
</div> </div>
{% endif %}
</div> </div>
</div> </div>
</div> </div>
@@ -1631,21 +1648,31 @@ document.addEventListener('DOMContentLoaded', function() {
document.getElementById('dv-new-coords').textContent = data.extended_stats.dv.new_coords; document.getElementById('dv-new-coords').textContent = data.extended_stats.dv.new_coords;
document.getElementById('dv-transfer-delta').textContent = data.extended_stats.dv.transfer_delta + ' МГц'; 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-planned').textContent = data.extended_stats.kubsat.planned_count;
document.getElementById('kubsat-conducted').textContent = data.extended_stats.kubsat.conducted_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-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-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 // Update Kubsat statistics - Проведено
updateKubsatProgressBar(data.extended_stats.kubsat); 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 // Update period info
updatePeriodInfo(data.date_from, data.date_to); updatePeriodInfo(data.date_from, data.date_to);
}) })
.catch(error => { .catch(error => {
console.error('Error loading extended stats:', error); console.error('Error loading extended stats:', error);
alert('Ошибка загрузки статистики'); // alert('Ошибка загрузки статистики');
}) })
.finally(() => { .finally(() => {
// Hide loading indicator // 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 += `<div class="progress-bar bg-success" role="progressbar" style="width: ${pct}%" title="Проведено: ${kubsat.conducted_count}">${kubsat.conducted_count}</div>`;
}
if (kubsat.canceled_gso_count > 0) {
const pct = Math.round((kubsat.canceled_gso_count / total) * 100);
html += `<div class="progress-bar bg-danger" role="progressbar" style="width: ${pct}%" title="Отменено ГСО: ${kubsat.canceled_gso_count}">${kubsat.canceled_gso_count}</div>`;
}
if (kubsat.canceled_kub_count > 0) {
const pct = Math.round((kubsat.canceled_kub_count / total) * 100);
html += `<div class="progress-bar bg-warning text-dark" role="progressbar" style="width: ${pct}%" title="Отменено МКА: ${kubsat.canceled_kub_count}">${kubsat.canceled_kub_count}</div>`;
}
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 // Update period info display
function updatePeriodInfo(dateFrom, dateTo) { function updatePeriodInfo(dateFrom, dateTo) {
const periodAlert = document.querySelector('#extendedStatsModal .alert-light'); const periodAlert = document.querySelector('#extendedStatsModal .alert-light');

View File

@@ -15,7 +15,7 @@ from django.urls import reverse
from django.views import View from django.views import View
from ..forms import SourceForm 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 ..utils import format_coords_display, parse_pagination_params
from ..permissions import PermissionRequiredMixin, permission_required from ..permissions import PermissionRequiredMixin, permission_required
@@ -419,7 +419,6 @@ class SourceListView(LoginRequiredMixin, View):
# Filter by source requests # Filter by source requests
if has_requests == "1": if has_requests == "1":
# Has requests - apply subfilters # Has requests - apply subfilters
from ..models import SourceRequest
from django.db.models import Exists, OuterRef from django.db.models import Exists, OuterRef
# Build subquery for filtering requests # Build subquery for filtering requests
@@ -812,6 +811,9 @@ class SourceListView(LoginRequiredMixin, View):
'object_infos': object_infos, 'object_infos': object_infos,
'polygon_coords': json.dumps(polygon_coords) if polygon_coords else None, 'polygon_coords': json.dumps(polygon_coords) if polygon_coords else None,
'full_width_page': True, '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) return render(request, "mainapp/source_list.html", context)

View File

@@ -313,37 +313,39 @@ class StatisticsView(PermissionRequiredMixin, TemplateView):
Получает статистику по Кубсатам из SourceRequest. Получает статистику по Кубсатам из SourceRequest.
Возвращает: Возвращает:
- planned_count: количество запланированных сеансов 1. Запланировано (по истории статуса 'planned'):
- conducted_count: количество проведённых - planned_count: всего запланировано
- canceled_gso_count: количество отменённых ГСО - conducted_count: проведено
- canceled_kub_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 для заявок # === 1. ЗАПЛАНИРОВАНО ===
requests_qs = SourceRequest.objects.all() # Получаем ID заявок, у которых в истории был статус 'planned' в периоде
history_planned_qs = SourceRequestStatusHistory.objects.filter(
# Фильтруем по дате создания или 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(
new_status='planned' new_status='planned'
) )
if date_from: 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: 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' в периоде # Также добавляем заявки, которые были созданы со статусом 'planned' в периоде
# (для случаев, когда заявка создана сразу с этим статусом без истории)
created_planned_qs = SourceRequest.objects.filter(status='planned') created_planned_qs = SourceRequest.objects.filter(status='planned')
if date_from: if date_from:
created_planned_qs = created_planned_qs.filter(created_at__date__gte=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) planned_count = len(planned_request_ids)
# Считаем статусы из истории для запланированных заявок # Считаем текущие статусы для запланированных заявок
conducted_count = 0 conducted_count = 0
canceled_gso_count = 0 canceled_gso_count = 0
canceled_kub_count = 0 canceled_kub_count = 0
wait_exec_count = 0
gso_fault_count = 0
if planned_request_ids: if planned_request_ids:
# Получаем историю статусов для запланированных заявок # Получаем текущие статусы запланированных заявок
status_history = SourceRequestStatusHistory.objects.filter( planned_requests = SourceRequest.objects.filter(id__in=planned_request_ids)
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)
# Считаем уникальные заявки по каждому статусу conducted_count = planned_requests.filter(status='conducted').count()
conducted_ids = set(status_history.filter(new_status='conducted').values_list('source_request_id', flat=True)) canceled_gso_count = planned_requests.filter(status='canceled_gso').count()
canceled_gso_ids = set(status_history.filter(new_status='canceled_gso').values_list('source_request_id', flat=True)) canceled_kub_count = planned_requests.filter(status='canceled_kub').count()
canceled_kub_ids = set(status_history.filter(new_status='canceled_kub').values_list('source_request_id', flat=True)) 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) result_received_count = conducted_requests.filter(status='result_received').count()
canceled_gso_count = len(canceled_gso_ids) processing_count = conducted_requests.filter(status='processing').count()
canceled_kub_count = len(canceled_kub_ids) 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 { return {
# Запланировано
'planned_count': planned_count, 'planned_count': planned_count,
'conducted_count': conducted_count, 'conducted_count': conducted_count,
'canceled_gso_count': canceled_gso_count, 'canceled_gso_count': canceled_gso_count,
'canceled_kub_count': canceled_kub_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): def get_extended_statistics(self, date_from, date_to):