Пофиксил журнал ошибок
This commit is contained in:
18
dbapp/mainapp/migrations/0030_issuetype_location_place.py
Normal file
18
dbapp/mainapp/migrations/0030_issuetype_location_place.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-16 08:09
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0029_dailyreport_location_place'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='issuetype',
|
||||
name='location_place',
|
||||
field=models.CharField(choices=[('kr', 'КР'), ('dv', 'ДВ')], default='kr', help_text='К какому комплексу принадлежит журнал', max_length=30, null=True, verbose_name='Комплекс'),
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,24 @@
|
||||
# Generated by Django 5.2.7 on 2025-12-16 08:19
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('mainapp', '0030_issuetype_location_place'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='dailyreport',
|
||||
name='date',
|
||||
field=models.DateField(db_index=True, help_text='Дата отчёта', verbose_name='Дата'),
|
||||
),
|
||||
migrations.AddConstraint(
|
||||
model_name='dailyreport',
|
||||
constraint=models.UniqueConstraint(fields=('date', 'location_place'), name='unique_daily_report_date_location'),
|
||||
),
|
||||
]
|
||||
@@ -10,7 +10,18 @@ class IssueType(models.Model):
|
||||
('error', 'Ошибка'),
|
||||
('malfunction', 'Неисправность'),
|
||||
]
|
||||
|
||||
PLACES = [
|
||||
("kr", "КР"),
|
||||
("dv", "ДВ")
|
||||
]
|
||||
location_place = models.CharField(
|
||||
max_length=30,
|
||||
choices=PLACES,
|
||||
null=True,
|
||||
default="kr",
|
||||
verbose_name="Комплекс",
|
||||
help_text="К какому комплексу принадлежит журнал",
|
||||
)
|
||||
name = models.CharField(max_length=255, verbose_name="Название")
|
||||
category = models.CharField(
|
||||
max_length=20,
|
||||
@@ -35,7 +46,6 @@ class DailyReport(models.Model):
|
||||
("dv", "ДВ")
|
||||
]
|
||||
date = models.DateField(
|
||||
unique=True,
|
||||
verbose_name="Дата",
|
||||
db_index=True,
|
||||
help_text="Дата отчёта"
|
||||
@@ -81,6 +91,12 @@ class DailyReport(models.Model):
|
||||
verbose_name = "Ежедневный отчёт"
|
||||
verbose_name_plural = "Ежедневные отчёты"
|
||||
ordering = ["-date"]
|
||||
constraints = [
|
||||
models.UniqueConstraint(
|
||||
fields=['date', 'location_place'],
|
||||
name='unique_daily_report_date_location'
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
class DowntimePeriod(models.Model):
|
||||
|
||||
@@ -86,27 +86,37 @@
|
||||
<h4>Журнал ошибок и неисправностей</h4>
|
||||
|
||||
<!-- Toolbar -->
|
||||
<div class="row mb-3">
|
||||
<div class="row mb-3" id="toolbar">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-wrap align-items-center gap-3">
|
||||
<!-- Выбор комплекса -->
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<label class="form-label mb-0 text-muted">Комплекс:</label>
|
||||
<select id="locationSelect" class="form-select form-select-sm" style="width: auto;">
|
||||
<option value="">-- Выберите --</option>
|
||||
{% for code, name in location_places %}
|
||||
<option value="{{ code }}">{{ name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="vr"></div>
|
||||
|
||||
<!-- Action buttons -->
|
||||
<div class="d-flex gap-2">
|
||||
{% if user|has_perm:'errors_report_create' %}
|
||||
<button class="btn btn-success btn-sm" onclick="openCreateModal()">
|
||||
<button class="btn btn-success btn-sm" onclick="openCreateModal()" id="btnAddRecord" disabled>
|
||||
<i class="bi bi-plus-lg"></i> Добавить запись
|
||||
</button>
|
||||
{% endif %}
|
||||
<!-- <button class="btn btn-primary btn-sm" onclick="loadData()">
|
||||
<i class="bi bi-arrow-clockwise"></i> Обновить
|
||||
</button> -->
|
||||
</div>
|
||||
|
||||
<!-- Filter Toggle Button -->
|
||||
<div>
|
||||
<button class="btn btn-outline-primary btn-sm" type="button" data-bs-toggle="offcanvas"
|
||||
data-bs-target="#offcanvasFilters" aria-controls="offcanvasFilters">
|
||||
data-bs-target="#offcanvasFilters" aria-controls="offcanvasFilters" id="btnFilters" disabled>
|
||||
<i class="bi bi-funnel"></i> Фильтры
|
||||
<span id="filterCounter" class="badge bg-danger" style="display: none;">0</span>
|
||||
</button>
|
||||
@@ -139,22 +149,6 @@
|
||||
<input type="date" id="dateTo" name="date_to" class="form-control form-control-sm">
|
||||
</div>
|
||||
</div>
|
||||
<!-- Location Filter -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">
|
||||
Комплекс
|
||||
</label>
|
||||
<div class="d-flex justify-content-between mb-1">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||
onclick="selectAllOptions('locationFilter', true)">Выбрать</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary"
|
||||
onclick="selectAllOptions('locationFilter', false)">Снять</button>
|
||||
</div>
|
||||
<select id="locationFilter" name="location_place" class="form-select form-select-sm" multiple size="2">
|
||||
<option value="kr">КР</option>
|
||||
<option value="dv">ДВ</option>
|
||||
</select>
|
||||
</div>
|
||||
<!-- Error Filter -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">
|
||||
@@ -167,9 +161,6 @@
|
||||
onclick="selectAllOptions('errorFilter', false)">Снять</button>
|
||||
</div>
|
||||
<select id="errorFilter" name="error_filter" class="form-select form-select-sm" multiple size="6">
|
||||
{% for e in errors %}
|
||||
<option value="{{ e.id }}">{{ e.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -185,9 +176,6 @@
|
||||
onclick="selectAllOptions('malfunctionFilter', false)">Снять</button>
|
||||
</div>
|
||||
<select id="malfunctionFilter" name="malfunction_filter" class="form-select form-select-sm" multiple size="6">
|
||||
{% for m in malfunctions %}
|
||||
<option value="{{ m.id }}">{{ m.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -204,7 +192,12 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive">
|
||||
<!-- Подсказка до выбора комплекса -->
|
||||
<div class="text-center text-muted py-5" id="selectLocationHint">
|
||||
<p class="mt-2">Выберите комплекс для отображения данных</p>
|
||||
</div>
|
||||
|
||||
<div class="table-responsive" id="tableContainer" style="display: none;">
|
||||
<table class="table table-bordered table-hover table-sm" id="reportTable">
|
||||
<thead>
|
||||
<tr id="headerRowTop">
|
||||
@@ -293,27 +286,31 @@
|
||||
|
||||
{% block extra_js %}
|
||||
<script>
|
||||
const issueTypes = {
|
||||
errors: [
|
||||
{% for e in errors %}
|
||||
{id: {{ e.id }}, name: "{{ e.name|escapejs }}"},
|
||||
{% endfor %}
|
||||
],
|
||||
malfunctions: [
|
||||
{% for m in malfunctions %}
|
||||
{id: {{ m.id }}, name: "{{ m.name|escapejs }}"},
|
||||
{% endfor %}
|
||||
]
|
||||
const locationPlaces = {
|
||||
{% for code, name in location_places %}
|
||||
'{{ code }}': '{{ name }}',
|
||||
{% endfor %}
|
||||
};
|
||||
|
||||
let issueTypes = {
|
||||
errors: [],
|
||||
malfunctions: []
|
||||
};
|
||||
|
||||
let currentData = [];
|
||||
let currentColumns = [];
|
||||
let currentLocation = null;
|
||||
let editModal = null;
|
||||
|
||||
// Инициализация
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
editModal = new bootstrap.Modal(document.getElementById('editModal'));
|
||||
loadData();
|
||||
|
||||
// Обработчик выбора комплекса
|
||||
const locationSelect = document.getElementById('locationSelect');
|
||||
locationSelect.addEventListener('change', function() {
|
||||
onLocationChange(this.value);
|
||||
});
|
||||
|
||||
// Добавляем обработчики для обновления счетчика фильтров
|
||||
const form = document.getElementById('filter-form');
|
||||
@@ -331,15 +328,63 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Обработка смены комплекса
|
||||
function onLocationChange(location) {
|
||||
const hint = document.getElementById('selectLocationHint');
|
||||
const tableContainer = document.getElementById('tableContainer');
|
||||
const btnAdd = document.getElementById('btnAddRecord');
|
||||
const btnFilters = document.getElementById('btnFilters');
|
||||
|
||||
if (!location) {
|
||||
// Сбрасываем состояние
|
||||
currentLocation = null;
|
||||
currentData = [];
|
||||
currentColumns = [];
|
||||
issueTypes = { errors: [], malfunctions: [] };
|
||||
|
||||
// Показываем hint, скрываем таблицу
|
||||
hint.style.display = 'block';
|
||||
tableContainer.style.display = 'none';
|
||||
|
||||
// Очищаем таблицу
|
||||
document.getElementById('reportBody').innerHTML = '';
|
||||
document.getElementById('headerRowTop').innerHTML = '';
|
||||
document.getElementById('headerRowBottom').innerHTML = '';
|
||||
|
||||
// Отключаем кнопки
|
||||
if (btnAdd) btnAdd.disabled = true;
|
||||
if (btnFilters) btnFilters.disabled = true;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
currentLocation = location;
|
||||
|
||||
// Скрываем hint, показываем таблицу
|
||||
hint.style.display = 'none';
|
||||
tableContainer.style.display = 'block';
|
||||
|
||||
// Включаем кнопки
|
||||
if (btnAdd) btnAdd.disabled = false;
|
||||
if (btnFilters) btnFilters.disabled = false;
|
||||
|
||||
// Сбрасываем фильтры при смене комплекса
|
||||
document.getElementById('dateFrom').value = '';
|
||||
document.getElementById('dateTo').value = '';
|
||||
selectAllOptions('errorFilter', false);
|
||||
selectAllOptions('malfunctionFilter', false);
|
||||
|
||||
// Загружаем данные
|
||||
loadData();
|
||||
}
|
||||
|
||||
// Загрузка данных
|
||||
async function loadData() {
|
||||
if (!currentLocation) return;
|
||||
|
||||
const dateFrom = document.getElementById('dateFrom').value;
|
||||
const dateTo = document.getElementById('dateTo').value;
|
||||
|
||||
// Получаем выбранные значения из мультиселектов
|
||||
const locationSelect = document.getElementById('locationFilter');
|
||||
const locationPlaces = Array.from(locationSelect.selectedOptions).map(opt => opt.value);
|
||||
|
||||
const errorSelect = document.getElementById('errorFilter');
|
||||
const errorFilters = Array.from(errorSelect.selectedOptions).map(opt => opt.value);
|
||||
|
||||
@@ -347,14 +392,10 @@ async function loadData() {
|
||||
const malfunctionFilters = Array.from(malfunctionSelect.selectedOptions).map(opt => opt.value);
|
||||
|
||||
let url = '{% url "mainapp:errors_report_api" %}?';
|
||||
url += `location_place=${currentLocation}&`;
|
||||
if (dateFrom) url += `date_from=${dateFrom}&`;
|
||||
if (dateTo) url += `date_to=${dateTo}&`;
|
||||
|
||||
// Добавляем множественные значения
|
||||
locationPlaces.forEach(loc => {
|
||||
url += `location_place=${loc}&`;
|
||||
});
|
||||
|
||||
errorFilters.forEach(err => {
|
||||
url += `error_filter=${err}&`;
|
||||
});
|
||||
@@ -370,6 +411,13 @@ async function loadData() {
|
||||
currentData = result.data;
|
||||
currentColumns = result.columns;
|
||||
|
||||
// Обновляем issueTypes для модального окна
|
||||
issueTypes.errors = currentColumns.filter(c => c.category === 'error');
|
||||
issueTypes.malfunctions = currentColumns.filter(c => c.category === 'malfunction');
|
||||
|
||||
// Обновляем фильтры
|
||||
updateFilterOptions();
|
||||
|
||||
renderTable();
|
||||
updateFilterCounter();
|
||||
} catch (error) {
|
||||
@@ -378,6 +426,36 @@ async function loadData() {
|
||||
}
|
||||
}
|
||||
|
||||
// Обновление опций фильтров на основе текущего комплекса
|
||||
function updateFilterOptions() {
|
||||
const errorSelect = document.getElementById('errorFilter');
|
||||
const malfunctionSelect = document.getElementById('malfunctionFilter');
|
||||
|
||||
// Сохраняем текущий выбор
|
||||
const selectedErrors = Array.from(errorSelect.selectedOptions).map(opt => opt.value);
|
||||
const selectedMalfunctions = Array.from(malfunctionSelect.selectedOptions).map(opt => opt.value);
|
||||
|
||||
// Обновляем ошибки
|
||||
errorSelect.innerHTML = '';
|
||||
issueTypes.errors.forEach(e => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = e.id;
|
||||
opt.textContent = e.name;
|
||||
if (selectedErrors.includes(String(e.id))) opt.selected = true;
|
||||
errorSelect.appendChild(opt);
|
||||
});
|
||||
|
||||
// Обновляем неисправности
|
||||
malfunctionSelect.innerHTML = '';
|
||||
issueTypes.malfunctions.forEach(m => {
|
||||
const opt = document.createElement('option');
|
||||
opt.value = m.id;
|
||||
opt.textContent = m.name;
|
||||
if (selectedMalfunctions.includes(String(m.id))) opt.selected = true;
|
||||
malfunctionSelect.appendChild(opt);
|
||||
});
|
||||
}
|
||||
|
||||
// Функция для выбора/снятия всех опций в select
|
||||
function selectAllOptions(selectId, selectAll) {
|
||||
const selectElement = document.getElementById(selectId);
|
||||
@@ -404,11 +482,12 @@ function resetFilters() {
|
||||
document.getElementById('dateTo').value = '';
|
||||
|
||||
// Снимаем выбор со всех мультиселектов
|
||||
selectAllOptions('locationFilter', false);
|
||||
selectAllOptions('errorFilter', false);
|
||||
selectAllOptions('malfunctionFilter', false);
|
||||
|
||||
loadData();
|
||||
if (currentLocation) {
|
||||
loadData();
|
||||
}
|
||||
|
||||
// Закрыть offcanvas
|
||||
const offcanvas = bootstrap.Offcanvas.getInstance(document.getElementById('offcanvasFilters'));
|
||||
@@ -425,10 +504,6 @@ function updateFilterCounter() {
|
||||
if (document.getElementById('dateFrom').value) filterCount++;
|
||||
if (document.getElementById('dateTo').value) filterCount++;
|
||||
|
||||
// Проверяем мультиселекты
|
||||
const locationSelect = document.getElementById('locationFilter');
|
||||
if (locationSelect.selectedOptions.length > 0) filterCount++;
|
||||
|
||||
const errorSelect = document.getElementById('errorFilter');
|
||||
if (errorSelect.selectedOptions.length > 0) filterCount++;
|
||||
|
||||
@@ -517,7 +592,6 @@ function renderTable() {
|
||||
topHtml += `
|
||||
<th rowspan="2" style="width: 80px;">Раб. ч/день</th>
|
||||
<th rowspan="2" style="width: 100px;">Раб. ч/нед.</th>
|
||||
<th rowspan="2" style="width: 80px;">Комплекс</th>
|
||||
<th rowspan="2" style="min-width: 150px;">Пояснение</th>
|
||||
<th rowspan="2" style="min-width: 150px;">Комментарий</th>
|
||||
`;
|
||||
@@ -583,7 +657,6 @@ function renderTable() {
|
||||
bodyHtml += `<td class="text-center align-middle weekly-hours-cell" rowspan="${info.rowspan}">${info.weeklyTotal}</td>`;
|
||||
}
|
||||
|
||||
bodyHtml += `<td class="text-center">${row.location_place_display || ''}</td>`;
|
||||
bodyHtml += `<td>${row.explanation || ''}</td>`;
|
||||
bodyHtml += `<td>${row.comment || ''}</td>`;
|
||||
bodyHtml += `</tr>`;
|
||||
@@ -604,11 +677,17 @@ function renderMarkCell(value) {
|
||||
|
||||
// Открыть модальное окно для создания
|
||||
function openCreateModal() {
|
||||
if (!currentLocation) {
|
||||
alert('Сначала выберите комплекс');
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('modalTitle').textContent = 'Новая запись';
|
||||
document.getElementById('editId').value = '';
|
||||
document.getElementById('editDate').value = new Date().toISOString().split('T')[0];
|
||||
document.getElementById('editDailyHours').value = 0;
|
||||
document.getElementById('editLocationPlace').value = 'kr';
|
||||
document.getElementById('editLocationPlace').value = currentLocation;
|
||||
document.getElementById('editLocationPlace').disabled = true; // Комплекс фиксирован
|
||||
document.getElementById('editExplanation').value = '';
|
||||
document.getElementById('editComment').value = '';
|
||||
|
||||
@@ -633,7 +712,8 @@ async function openEditModal(id) {
|
||||
document.getElementById('editId').value = row.id;
|
||||
document.getElementById('editDate').value = row.date;
|
||||
document.getElementById('editDailyHours').value = row.daily_work_hours || 0;
|
||||
document.getElementById('editLocationPlace').value = row.location_place || 'kr';
|
||||
document.getElementById('editLocationPlace').value = row.location_place || currentLocation;
|
||||
document.getElementById('editLocationPlace').disabled = true; // Комплекс фиксирован
|
||||
document.getElementById('editExplanation').value = row.explanation || '';
|
||||
document.getElementById('editComment').value = row.comment || '';
|
||||
|
||||
@@ -694,27 +774,35 @@ function removeDowntimeRow(btn) {
|
||||
// Рендер чекбоксов ошибок/неисправностей
|
||||
function renderIssueCheckboxes(row) {
|
||||
let errorsHtml = '';
|
||||
issueTypes.errors.forEach(e => {
|
||||
const checked = row[`issue_${e.id}`] ? 'checked' : '';
|
||||
errorsHtml += `
|
||||
<div class="form-check">
|
||||
<input class="form-check-input issue-check" type="checkbox" id="issue_${e.id}" data-issue-id="${e.id}" ${checked}>
|
||||
<label class="form-check-label" for="issue_${e.id}">${e.name}</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
if (issueTypes.errors.length === 0) {
|
||||
errorsHtml = '<p class="text-muted small">Нет ошибок для этого комплекса</p>';
|
||||
} else {
|
||||
issueTypes.errors.forEach(e => {
|
||||
const checked = row[`issue_${e.id}`] ? 'checked' : '';
|
||||
errorsHtml += `
|
||||
<div class="form-check">
|
||||
<input class="form-check-input issue-check" type="checkbox" id="issue_${e.id}" data-issue-id="${e.id}" ${checked}>
|
||||
<label class="form-check-label" for="issue_${e.id}">${e.name}</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
document.getElementById('editErrors').innerHTML = errorsHtml;
|
||||
|
||||
let malfHtml = '';
|
||||
issueTypes.malfunctions.forEach(m => {
|
||||
const checked = row[`issue_${m.id}`] ? 'checked' : '';
|
||||
malfHtml += `
|
||||
<div class="form-check">
|
||||
<input class="form-check-input issue-check" type="checkbox" id="issue_${m.id}" data-issue-id="${m.id}" ${checked}>
|
||||
<label class="form-check-label" for="issue_${m.id}">${m.name}</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
if (issueTypes.malfunctions.length === 0) {
|
||||
malfHtml = '<p class="text-muted small">Нет неисправностей для этого комплекса</p>';
|
||||
} else {
|
||||
issueTypes.malfunctions.forEach(m => {
|
||||
const checked = row[`issue_${m.id}`] ? 'checked' : '';
|
||||
malfHtml += `
|
||||
<div class="form-check">
|
||||
<input class="form-check-input issue-check" type="checkbox" id="issue_${m.id}" data-issue-id="${m.id}" ${checked}>
|
||||
<label class="form-check-label" for="issue_${m.id}">${m.name}</label>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
}
|
||||
document.getElementById('editMalfunctions').innerHTML = malfHtml;
|
||||
}
|
||||
|
||||
@@ -751,7 +839,7 @@ async function saveRecord() {
|
||||
id: document.getElementById('editId').value || null,
|
||||
date: date,
|
||||
daily_work_hours: parseFloat(document.getElementById('editDailyHours').value) || 0,
|
||||
location_place: document.getElementById('editLocationPlace').value,
|
||||
location_place: currentLocation, // Используем текущий выбранный комплекс
|
||||
explanation: document.getElementById('editExplanation').value,
|
||||
comment: document.getElementById('editComment').value,
|
||||
downtimes: collectDowntimes(),
|
||||
|
||||
@@ -17,10 +17,8 @@ class ErrorsReportView(TemplateView, LoginRequiredMixin, PermissionRequiredMixin
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super().get_context_data(**kwargs)
|
||||
context['issue_types'] = IssueType.objects.all().order_by('category', 'name')
|
||||
context['errors'] = context['issue_types'].filter(category='error')
|
||||
context['malfunctions'] = context['issue_types'].filter(category='malfunction')
|
||||
context['full_width_page'] = True
|
||||
context['location_places'] = DailyReport.PLACES
|
||||
return context
|
||||
|
||||
|
||||
@@ -31,18 +29,24 @@ class ErrorsReportAPIView(View, LoginRequiredMixin, PermissionRequiredMixin):
|
||||
# Получаем параметры фильтрации
|
||||
date_from = request.GET.get('date_from')
|
||||
date_to = request.GET.get('date_to')
|
||||
location_places = request.GET.getlist('location_place')
|
||||
location_place = request.GET.get('location_place')
|
||||
error_filters = request.GET.getlist('error_filter')
|
||||
malfunction_filters = request.GET.getlist('malfunction_filter')
|
||||
|
||||
reports = DailyReport.objects.all()
|
||||
# location_place обязателен
|
||||
if not location_place:
|
||||
return JsonResponse({
|
||||
'data': [],
|
||||
'columns': [],
|
||||
'error': 'Выберите комплекс'
|
||||
})
|
||||
|
||||
reports = DailyReport.objects.filter(location_place=location_place)
|
||||
|
||||
if date_from:
|
||||
reports = reports.filter(date__gte=date_from)
|
||||
if date_to:
|
||||
reports = reports.filter(date__lte=date_to)
|
||||
if location_places:
|
||||
reports = reports.filter(location_place__in=location_places)
|
||||
|
||||
# Фильтрация по ошибкам/неисправностям
|
||||
if error_filters:
|
||||
@@ -52,8 +56,8 @@ class ErrorsReportAPIView(View, LoginRequiredMixin, PermissionRequiredMixin):
|
||||
|
||||
reports = reports.prefetch_related('downtime_periods', 'issue_marks__issue_type').distinct()
|
||||
|
||||
# Получаем все типы ошибок/неисправностей
|
||||
issue_types = IssueType.objects.all().order_by('category', 'name')
|
||||
# Получаем типы ошибок/неисправностей для выбранного комплекса
|
||||
issue_types = IssueType.objects.filter(location_place=location_place).order_by('category', 'name')
|
||||
|
||||
data = []
|
||||
for report in reports:
|
||||
@@ -83,7 +87,7 @@ class ErrorsReportAPIView(View, LoginRequiredMixin, PermissionRequiredMixin):
|
||||
'location_place_display': report.get_location_place_display() if report.location_place else '',
|
||||
}
|
||||
|
||||
# Добавляем отметки по каждому типу
|
||||
# Добавляем отметки по каждому типу (только для выбранного комплекса)
|
||||
for it in issue_types:
|
||||
row[f'issue_{it.id}'] = marks_dict.get(it.id, False)
|
||||
|
||||
@@ -124,13 +128,27 @@ class ErrorsReportSaveAPIView(View):
|
||||
return JsonResponse({'success': False, 'error': 'Неверный формат даты'}, status=400)
|
||||
|
||||
report_id = data.get('id')
|
||||
location_place = data.get('location_place', 'kr')
|
||||
|
||||
# Проверка на дублирование даты при создании новой записи
|
||||
# Проверка на дублирование даты + комплекса при создании новой записи
|
||||
if not report_id:
|
||||
if DailyReport.objects.filter(date=report_date).exists():
|
||||
if DailyReport.objects.filter(date=report_date, location_place=location_place).exists():
|
||||
place_display = dict(DailyReport.PLACES).get(location_place, location_place)
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': f'Запись за {report_date.strftime("%d.%m.%Y")} уже существует'
|
||||
'error': f'Запись за {report_date.strftime("%d.%m.%Y")} для комплекса {place_display} уже существует'
|
||||
}, status=400)
|
||||
else:
|
||||
# При обновлении проверяем, не занята ли комбинация другой записью
|
||||
existing = DailyReport.objects.filter(
|
||||
date=report_date,
|
||||
location_place=location_place
|
||||
).exclude(id=report_id).first()
|
||||
if existing:
|
||||
place_display = dict(DailyReport.PLACES).get(location_place, location_place)
|
||||
return JsonResponse({
|
||||
'success': False,
|
||||
'error': f'Запись за {report_date.strftime("%d.%m.%Y")} для комплекса {place_display} уже существует'
|
||||
}, status=400)
|
||||
|
||||
with transaction.atomic():
|
||||
|
||||
Reference in New Issue
Block a user