refactor: switch equipment_detail from CSR to SSR

- Updated equipment_detail view to load data from DB (SSR)
- Added select_related and prefetch_related for optimization
- Implemented can_manage permission check in view
- Removed loadEquipmentDetail() JavaScript API call
- Added breadcrumb navigation
- Kept action handlers (maintenance, transfer, write-off)
- Added department loading for transfer modal
- Now consistent with request_detail SSR approach
This commit is contained in:
admin i 2025-12-15 09:54:31 +03:00
parent 04e46d98d4
commit c7bc99398f
2 changed files with 63 additions and 37 deletions

View File

@ -195,11 +195,48 @@ def equipment_list(request):
def equipment_detail(request, pk):
"""Детальная страница оборудования.
Данные загружаются через API:
- /api/procurement/equipment/{pk}/
Загружает данные из БД и передаёт в шаблон.
Права на управление (can_manage):
- Админы/Staff → любое оборудование
- Начальник отдела → оборудование своего отдела
- Ответственный за оборудование → своё оборудование
"""
from django.shortcuts import get_object_or_404
from procurement.models import Equipment
equipment = get_object_or_404(
Equipment.objects.select_related(
'department',
'department__head',
'category',
'responsible_person',
'procurement_item',
'procurement_item__request'
).prefetch_related(
'maintenance_records',
'transfer_logs__from_department',
'transfer_logs__to_department',
'transfer_logs__from_person',
'transfer_logs__to_person'
),
pk=pk
)
user = request.user
# Проверяем права на управление
can_manage = False
if user.is_superuser or user.is_staff:
can_manage = True
elif equipment.department and equipment.department.head == user:
can_manage = True
elif equipment.responsible_person == user:
can_manage = True
return render(request, 'procurement/equipment_detail.html', {
'equipment_id': pk,
'equipment': equipment,
'can_manage': can_manage,
})

View File

@ -347,44 +347,33 @@
{% block extra_js %}
<script>
// Получаем ID оборудования из URL
const equipmentId = window.location.pathname.split('/').filter(x => x && !isNaN(x))[0];
// Устанавливаем заголовок страницы
document.title = '{{ equipment.name|escapejs }}';
// Загрузка данных при старте
document.addEventListener('DOMContentLoaded', () => {
if (equipmentId) {
loadEquipmentDetail();
{% if can_manage %}
const equipmentId = {{ equipment.id }};
// Загрузка списка отделов для перевода
document.addEventListener('DOMContentLoaded', async () => {
try {
const resp = await fetch('/api/v1/departments/');
if (resp.ok) {
const data = await resp.json();
const departments = Array.isArray(data) ? data : (data.results || []);
const select = document.getElementById('transferDepartment');
departments.forEach(dept => {
const option = document.createElement('option');
option.value = dept.id;
option.textContent = dept.name;
select.appendChild(option);
});
}
} catch (e) {
console.error('Error loading departments:', e);
}
});
async function loadEquipmentDetail() {
try {
const resp = await fetch(`/api/v1/procurement/equipment/${equipmentId}/`);
if (!resp.ok) {
throw new Error('Equipment not found');
}
const equipment = await resp.json();
document.title = equipment.name;
// Обновляем хлебные крошки
const breadcrumb = document.querySelector('.breadcrumb-item.active');
if (breadcrumb) {
breadcrumb.textContent = equipment.inventory_number;
}
} catch (e) {
console.error('Error loading equipment:', e);
document.querySelector('.container').innerHTML = `
<div class="alert alert-danger">
<i class="bi-exclamation-triangle me-2"></i>
Ошибка загрузки оборудования
</div>
`;
}
}
{% if can_manage %}
// Обслуживание
document.getElementById('maintenanceForm')?.addEventListener('submit', async (e) => {
e.preventDefault();