Доделал статистику. Поправил разрешение 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
from django.urls import reverse
action_buttons_html = f'''
<a href="{reverse('mainapp:fill_lyngsat_data')}" class="btn btn-secondary btn-sm" title="Заполнить данные Lyngsat">
<i class="bi bi-cloud-download"></i> Добавить данные
</a>
<a href="{reverse('mainapp:link_lyngsat')}" class="btn btn-primary btn-sm" title="Привязать источники LyngSat">
<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>
'''
from mainapp.permissions import has_permission
action_buttons_html = ''
if has_permission(self.request.user, 'lyngsat_parse'):
action_buttons_html = f'''
<a href="{reverse('mainapp:fill_lyngsat_data')}" class="btn btn-secondary btn-sm" title="Заполнить данные Lyngsat">
<i class="bi bi-cloud-download"></i> Добавить данные
</a>
<a href="{reverse('mainapp:link_lyngsat')}" class="btn btn-primary btn-sm" title="Привязать источники LyngSat">
<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
# 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', 'Запланировано'),
('canceled_gso', 'Отменено ГСО'),
('canceled_kub', 'Отменено МКА'),
('error_gso', 'Ошибка ГСО'),
('error_kub', 'Ошибка МКА'),
('wait_exec', 'Ожидают проведения'),
('suggested', 'Предложено'),
('gso_fault', 'Не проведены по вине ГСО'),
('conducted', 'Проведён'),
('successful', 'Успешно'),
('no_correlation', 'Нет корреляции'),

View File

@@ -81,23 +81,17 @@
<div class="col-md-3 mb-3">
<label for="requestStatus" class="form-label">Статус</label>
<select class="form-select" id="requestStatus" name="status">
<option value="planned">Запланировано</option>
<option value="conducted">Проведён</option>
<option value="successful">Успешно</option>
<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>
{% for value, label in status_choices %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<div class="col-md-3 mb-3">
<label for="requestPriority" class="form-label">Приоритет</label>
<select class="form-select" id="requestPriority" name="priority">
<option value="low">Низкий</option>
<option value="medium" selected>Средний</option>
<option value="high">Высокий</option>
{% for value, label in priority_choices %}
<option value="{{ value }}" {% if value == 'medium' %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
</div>

View File

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

View File

@@ -558,86 +558,103 @@
<!-- Kubsat Statistics -->
<div class="row">
<div class="col-12">
<div class="card border-secondary">
<div class="card-header bg-secondary text-white">
<strong>Кубсаты</strong>
<!-- 1. Запланировано -->
<div class="col-md-6 mb-4">
<div class="card h-100 border-primary">
<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 class="card-body">
<div class="row text-center">
<div class="col-md-3 mb-3 mb-md-0">
<div class="p-3 bg-light rounded">
<div class="fs-3 fw-bold text-primary" id="kubsat-planned">{{ extended_stats.kubsat.planned_count }}</div>
<div class="text-muted small">
<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 class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
<span class="text-muted">
<i class="bi bi-check-circle text-success"></i> Проведено:
</span>
<span class="badge bg-success" id="kubsat-conducted">{{ extended_stats.kubsat.conducted_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-x-circle text-danger"></i> Отменено (ГСО):
</span>
<span class="badge bg-danger" id="kubsat-canceled-gso">{{ extended_stats.kubsat.canceled_gso_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-x-circle text-warning"></i> Отменено (МКА):
</span>
<span class="badge bg-warning text-dark" id="kubsat-canceled-kub">{{ extended_stats.kubsat.canceled_kub_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-hourglass-split text-info"></i> Ожидают проведения:
</span>
<span class="badge bg-info" id="kubsat-wait-exec">{{ extended_stats.kubsat.wait_exec_count }}</span>
</div>
<div class="d-flex justify-content-between align-items-center">
<span class="text-muted">
<i class="bi bi-exclamation-triangle text-secondary"></i> Не проведены (вина ГСО):
</span>
<span class="badge bg-secondary" id="kubsat-gso-fault">{{ extended_stats.kubsat.gso_fault_count }}</span>
</div>
</div>
</div>
</div>
<!-- Progress bar for Kubsat -->
{% if extended_stats.kubsat.planned_count > 0 %}
<div class="mt-4">
<div class="d-flex justify-content-between mb-1">
<small class="text-muted">Распределение статусов</small>
<small class="text-muted">Всего: {{ extended_stats.kubsat.planned_count }}</small>
</div>
<div class="progress" style="height: 25px;">
{% 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 %}
<div class="progress-bar bg-success" role="progressbar"
style="width: {% widthratio conducted total 100 %}%"
title="Проведено: {{ conducted }}">
{{ conducted }}
</div>
{% endif %}
{% if canceled_gso > 0 %}
<div class="progress-bar bg-danger" role="progressbar"
style="width: {% widthratio canceled_gso total 100 %}%"
title="Отменено ГСО: {{ canceled_gso }}">
{{ canceled_gso }}
</div>
{% endif %}
{% if canceled_kub > 0 %}
<div class="progress-bar bg-warning text-dark" role="progressbar"
style="width: {% widthratio canceled_kub total 100 %}%"
title="Отменено МКА: {{ canceled_kub }}">
{{ canceled_kub }}
</div>
{% endif %}
{% endwith %}
</div>
<div class="d-flex justify-content-center mt-2">
<small class="me-3"><span class="badge bg-success"></span> Проведено</small>
<small class="me-3"><span class="badge bg-danger"></span> Отменено ГСО</small>
<small><span class="badge bg-warning text-dark"></span> Отменено МКА</small>
</div>
<!-- 2. Проведено -->
<div class="col-md-6 mb-4">
<div class="card h-100 border-success">
<div class="card-header bg-success text-white">
<strong><i class="bi bi-check2-circle"></i> Проведено</strong>
<span class="badge bg-light text-success float-end fs-6" id="kubsat-total-conducted">{{ extended_stats.kubsat.total_conducted_count }}</span>
</div>
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-2 pb-2 border-bottom">
<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>
{% endif %}
</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-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 += `<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
function updatePeriodInfo(dateFrom, dateTo) {
const periodAlert = document.querySelector('#extendedStatsModal .alert-light');

View File

@@ -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)

View File

@@ -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()
conducted_count = len(conducted_ids)
canceled_gso_count = len(canceled_gso_ids)
canceled_kub_count = len(canceled_kub_ids)
# === 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)
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):