Files
ACORE/apps/power_assets/views.py
T
2026-09-21 20:37:18 +03:00

227 lines
8.5 KiB
Python

from django.contrib.auth.decorators import login_required
from django.db import models
from django.db.models import Prefetch
from django.http import HttpResponseForbidden
from django.shortcuts import get_object_or_404
from django.utils import timezone
from django.views.decorators.http import require_POST
from apps.locations.models.base import Location
from apps.locations.models.pharmacies import Pharmacy
from core.accounting_portal import render_accounting_portal
from core.models.directories import Directory
from core.portal_access import user_can_access
from core.utils.pagination import paginate
from core.location_access import accessible_locations, scoped_queryset, require_location
from .models import PowerAsset, PowerAssetService
def _can_manage_power(user) -> bool:
return user_can_access(user, 'utilities')
def _locations_qs(user):
return (
accessible_locations(user).filter(is_active=True)
.select_related('location_region')
.prefetch_related(
models.Prefetch(
'pharmacy',
queryset=Pharmacy.objects.select_related('organization'),
),
)
.order_by('location_type', 'address')
)
def _directory_choices(type_code):
return list(
Directory.objects.filter(directory_type__code=type_code, is_active=True)
.order_by('name')
.values_list('id', 'name')
)
def _power_assets_context(request, **extra):
type_filter = (request.GET.get('power_type') or '').strip()
location_filter = (request.GET.get('power_location') or '').strip()
status_filter = (request.GET.get('power_status') or '').strip()
due_only = (request.GET.get('power_due') or '').strip() == '1'
qs = (
scoped_queryset(request.user, PowerAsset.objects.all()).select_related(
'asset_type', 'status', 'location', 'location__location_region',
)
.prefetch_related(
Prefetch(
'service_logs',
queryset=PowerAssetService.objects.select_related('created_by', 'performed_by')
.order_by('-service_date', '-id'),
),
)
.order_by('location__address', 'asset_type__name', 'model_name')
)
if type_filter.isdigit():
qs = qs.filter(asset_type_id=int(type_filter))
if location_filter.isdigit():
qs = qs.filter(location_id=int(location_filter))
if status_filter.isdigit():
qs = qs.filter(status_id=int(status_filter))
if due_only:
qs = qs.filter(next_service_at__isnull=False, next_service_at__lte=timezone.localdate())
page = paginate(request, qs, page_param='power_page', size_param='power_per_page')
overdue_count = scoped_queryset(request.user, PowerAsset.objects.all()).filter(
next_service_at__isnull=False,
next_service_at__lt=timezone.localdate(),
).count()
ctx = {
'power_assets_page': page,
'power_asset_types': _directory_choices('power_asset_type'),
'power_asset_statuses': _directory_choices('power_asset_status'),
'power_service_kinds': PowerAssetService.ServiceKind.choices,
'power_locations': _locations_qs(request.user),
'power_type_filter': type_filter,
'power_location_filter': location_filter,
'power_status_filter': status_filter,
'power_due_filter': due_only,
'power_today': timezone.localdate(),
'power_counts': {
'total': scoped_queryset(request.user, PowerAsset.objects.all()).count(),
'inverter': scoped_queryset(request.user, PowerAsset.objects.all()).filter(asset_type__code='inverter').count(),
'generator': scoped_queryset(request.user, PowerAsset.objects.all()).filter(asset_type__code='generator').count(),
'overdue': overdue_count,
},
}
ctx.update(extra)
return ctx
def _parse_asset_form(request, asset: PowerAsset | None = None) -> tuple[PowerAsset | None, str]:
asset_type_id = request.POST.get('asset_type') or None
location_id = request.POST.get('location_id') or None
status_id = request.POST.get('status') or None
model_name = (request.POST.get('model_name') or '').strip()
if not asset_type_id or not location_id or not model_name:
return None, 'Укажите тип, объект и модель.'
require_location(request.user, location_id)
obj = asset or PowerAsset()
obj.asset_type_id = asset_type_id
obj.location_id = location_id
if status_id:
obj.status_id = status_id
obj.brand = (request.POST.get('brand') or '').strip()
obj.model_name = model_name
obj.serial_number = (request.POST.get('serial_number') or '').strip()
obj.inventory_number = (request.POST.get('inventory_number') or '').strip()
obj.fuel_type = (request.POST.get('fuel_type') or '').strip()
obj.notes = (request.POST.get('notes') or '').strip()
obj.installed_at = request.POST.get('installed_at') or None
obj.warranty_until = request.POST.get('warranty_until') or None
for field, raw in (
('capacity_kw', request.POST.get('capacity_kw')),
('battery_capacity_kwh', request.POST.get('battery_capacity_kwh')),
):
val = (raw or '').strip().replace(',', '.')
if val:
try:
setattr(obj, field, val)
except Exception:
return None, f'Некорректное значение: {field}'
else:
setattr(obj, field, None)
if not asset:
obj.created_by = request.user
return obj, ''
def _parse_service_form(request, asset: PowerAsset) -> tuple[PowerAssetService | None, str]:
description = (request.POST.get('description') or '').strip()
service_date = request.POST.get('service_date') or None
kind = request.POST.get('kind') or PowerAssetService.ServiceKind.MAINTENANCE
if not description:
return None, 'Укажите, что сделано при обслуживании.'
if kind not in dict(PowerAssetService.ServiceKind.choices):
return None, 'Некорректный вид работ.'
log = PowerAssetService(
asset=asset,
service_date=service_date or timezone.localdate(),
kind=kind,
description=description,
performed_by_name=(request.POST.get('performed_by_name') or '').strip(),
performed_by=request.user,
next_service_at=request.POST.get('next_service_at') or None,
notes=(request.POST.get('notes') or '').strip(),
created_by=request.user,
)
hours = (request.POST.get('engine_hours') or '').strip().replace(',', '.')
cost = (request.POST.get('cost') or '').strip().replace(',', '.')
if hours:
try:
log.engine_hours = hours
except Exception:
return None, 'Некорректные моточасы.'
if cost:
try:
log.cost = cost
except Exception:
return None, 'Некорректная стоимость.'
return log, ''
@login_required
@require_POST
def power_asset_create_view(request):
if not _can_manage_power(request.user):
return HttpResponseForbidden()
obj, err = _parse_asset_form(request)
extra = {}
if err:
extra['power_form_error'] = err
else:
obj.save()
extra['power_form_message'] = f'Добавлено: {obj.label}'
return render_accounting_portal(request, 'power', **extra)
@login_required
@require_POST
def power_asset_update_view(request, pk):
if not _can_manage_power(request.user):
return HttpResponseForbidden()
asset = get_object_or_404(scoped_queryset(request.user, PowerAsset.objects.all()), pk=pk)
obj, err = _parse_asset_form(request, asset)
extra = {}
if err:
extra['power_form_error'] = err
else:
obj.save()
extra['power_form_message'] = 'Сохранено.'
return render_accounting_portal(request, 'power', **extra)
@login_required
@require_POST
def power_asset_service_create_view(request, pk):
if not _can_manage_power(request.user):
return HttpResponseForbidden()
asset = get_object_or_404(scoped_queryset(request.user, PowerAsset.objects.all()), pk=pk)
log, err = _parse_service_form(request, asset)
extra = {}
if err:
extra['power_form_error'] = err
else:
log.save()
asset.refresh_service_dates()
extra['power_form_message'] = f'Обслуживание записано: {log.get_kind_display()}'
extra['power_open_asset_id'] = asset.pk
return render_accounting_portal(request, 'power', **extra)