227 lines
8.5 KiB
Python
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)
|