16bf2cf1c7
Конфиг счётчиков и день/ночь-тарифы, учёт энергосистем (генераторы, инверторы), доступ к разделу РРО/ПРРО. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
174 lines
7.5 KiB
Python
174 lines
7.5 KiB
Python
"""Учёт инверторов и генераторов с привязкой к объектам (аптека / офис / склад)."""
|
|
from django.conf import settings
|
|
from django.db import models
|
|
from django.utils import timezone
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from core.models import BaseModel
|
|
from core.models.directories import directory_default
|
|
|
|
|
|
class PowerAsset(BaseModel):
|
|
"""Единица энергооборудования коммунального отдела."""
|
|
|
|
asset_type = models.ForeignKey(
|
|
'core.Directory', on_delete=models.PROTECT,
|
|
limit_choices_to={'directory_type__code': 'power_asset_type', 'is_active': True},
|
|
related_name='power_assets', verbose_name=_('Тип'),
|
|
)
|
|
brand = models.CharField(_('Бренд'), max_length=100, blank=True)
|
|
model_name = models.CharField(_('Модель'), max_length=120)
|
|
serial_number = models.CharField(
|
|
_('Серийный номер'), max_length=100, blank=True,
|
|
help_text=_('Если известен — заполните для учёта.'),
|
|
)
|
|
inventory_number = models.CharField(
|
|
_('Инвентарный номер'), max_length=50, blank=True,
|
|
)
|
|
|
|
location = models.ForeignKey(
|
|
'locations.Location', on_delete=models.PROTECT,
|
|
related_name='power_assets', verbose_name=_('Объект'),
|
|
help_text=_('Аптека, офис или склад, где установлено оборудование.'),
|
|
)
|
|
status = models.ForeignKey(
|
|
'core.Directory', on_delete=models.PROTECT,
|
|
limit_choices_to={'directory_type__code': 'power_asset_status', 'is_active': True},
|
|
related_name='power_assets_by_status', verbose_name=_('Статус'),
|
|
default=directory_default('power_asset_status', 'active'),
|
|
)
|
|
|
|
capacity_kw = models.DecimalField(
|
|
_('Мощность, кВт'), max_digits=8, decimal_places=2,
|
|
null=True, blank=True,
|
|
)
|
|
fuel_type = models.CharField(
|
|
_('Тип топлива'), max_length=50, blank=True,
|
|
help_text=_('Для генераторов: бензин, дизель, газ…'),
|
|
)
|
|
battery_capacity_kwh = models.DecimalField(
|
|
_('Ёмкость АКБ, кВт·ч'), max_digits=8, decimal_places=2,
|
|
null=True, blank=True,
|
|
help_text=_('Для инверторных систем с накопителем.'),
|
|
)
|
|
|
|
installed_at = models.DateField(_('Дата установки'), null=True, blank=True)
|
|
warranty_until = models.DateField(_('Гарантия до'), null=True, blank=True)
|
|
last_serviced_at = models.DateField(_('Последнее обслуживание'), null=True, blank=True)
|
|
next_service_at = models.DateField(
|
|
_('Следующее обслуживание'), null=True, blank=True,
|
|
help_text=_('Плановая дата; обновляется из журнала обслуживания.'),
|
|
)
|
|
notes = models.TextField(_('Примечания'), blank=True)
|
|
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='power_assets_created', verbose_name=_('Кто добавил'),
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = _('Энергооборудование')
|
|
verbose_name_plural = _('Энергооборудование')
|
|
ordering = ['location', 'asset_type__name', 'model_name']
|
|
indexes = [
|
|
models.Index(fields=['location', 'asset_type']),
|
|
models.Index(fields=['status']),
|
|
models.Index(fields=['next_service_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
brand = f'{self.brand} ' if self.brand else ''
|
|
return f'{self.asset_type.name}: {brand}{self.model_name}'.strip()
|
|
|
|
@property
|
|
def label(self) -> str:
|
|
parts = [self.asset_type.name]
|
|
if self.brand:
|
|
parts.append(self.brand)
|
|
parts.append(self.model_name)
|
|
if self.serial_number:
|
|
parts.append(f'[{self.serial_number}]')
|
|
return ' '.join(parts)
|
|
|
|
@property
|
|
def service_overdue(self) -> bool:
|
|
if not self.next_service_at:
|
|
return False
|
|
return self.next_service_at < timezone.localdate()
|
|
|
|
def refresh_service_dates(self):
|
|
"""Пересчитать last/next из журнала обслуживания."""
|
|
latest = self.service_logs.order_by('-service_date', '-id').first()
|
|
self.last_serviced_at = latest.service_date if latest else None
|
|
upcoming = (
|
|
self.service_logs.exclude(next_service_at__isnull=True)
|
|
.order_by('-service_date', '-id')
|
|
.values_list('next_service_at', flat=True)
|
|
.first()
|
|
)
|
|
self.next_service_at = upcoming
|
|
self.save(update_fields=['last_serviced_at', 'next_service_at', 'updated_at'])
|
|
|
|
|
|
class PowerAssetService(BaseModel):
|
|
"""Запись обслуживания / ТО энергооборудования."""
|
|
|
|
class ServiceKind(models.TextChoices):
|
|
INSPECTION = 'inspection', _('Осмотр / проверка')
|
|
MAINTENANCE = 'maintenance', _('Плановое ТО')
|
|
OIL_CHANGE = 'oil_change', _('Замена масла / фильтров')
|
|
BATTERY = 'battery', _('АКБ / инвертор')
|
|
TEST_RUN = 'test_run', _('Пробный запуск')
|
|
REPAIR = 'repair', _('Ремонт')
|
|
OTHER = 'other', _('Прочее')
|
|
|
|
asset = models.ForeignKey(
|
|
PowerAsset, on_delete=models.CASCADE,
|
|
related_name='service_logs', verbose_name=_('Оборудование'),
|
|
)
|
|
service_date = models.DateField(_('Дата'), default=timezone.localdate)
|
|
kind = models.CharField(
|
|
_('Вид работ'), max_length=32,
|
|
choices=ServiceKind.choices, default=ServiceKind.MAINTENANCE,
|
|
)
|
|
description = models.TextField(_('Что сделано'))
|
|
performed_by_name = models.CharField(
|
|
_('Исполнитель'), max_length=120, blank=True,
|
|
help_text=_('ФИО или подрядчик, если не сотрудник портала.'),
|
|
)
|
|
performed_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='power_services_done', verbose_name=_('Сотрудник'),
|
|
)
|
|
engine_hours = models.DecimalField(
|
|
_('Моточасы'), max_digits=10, decimal_places=1,
|
|
null=True, blank=True,
|
|
help_text=_('Для генераторов — показания на момент ТО.'),
|
|
)
|
|
cost = models.DecimalField(
|
|
_('Стоимость'), max_digits=12, decimal_places=2,
|
|
null=True, blank=True,
|
|
)
|
|
next_service_at = models.DateField(
|
|
_('Следующее ТО'), null=True, blank=True,
|
|
)
|
|
notes = models.CharField(_('Комментарий'), max_length=255, blank=True)
|
|
created_by = models.ForeignKey(
|
|
settings.AUTH_USER_MODEL, on_delete=models.SET_NULL,
|
|
null=True, blank=True,
|
|
related_name='power_services_created', verbose_name=_('Кто внёс'),
|
|
)
|
|
|
|
class Meta:
|
|
verbose_name = _('Обслуживание энергооборудования')
|
|
verbose_name_plural = _('Журнал обслуживания')
|
|
ordering = ['-service_date', '-id']
|
|
indexes = [
|
|
models.Index(fields=['asset', 'service_date']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f'{self.asset} — {self.get_kind_display()} ({self.service_date:%d.%m.%Y})'
|