Files
ACORE/templates/pages/power-assets.html
T
lokses 16bf2cf1c7 эксплуатация: показания счётчиков, энергоактивы и РРО
Конфиг счётчиков и день/ночь-тарифы, учёт энергосистем (генераторы,
инверторы), доступ к разделу РРО/ПРРО.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-09 18:03:44 +03:00

359 lines
30 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{% load i18n %}
<div>
<div style="display:flex; align-items:flex-start; justify-content:space-between; gap:1rem; flex-wrap:wrap; margin-bottom:1.25rem;">
<div>
<h2 style="font-size:1.0625rem; font-weight:500; margin:0;">{% trans "Энергооборудование" %}</h2>
<p style="font-size:0.8125rem; color:var(--color-muted-foreground); margin:0.25rem 0 0;">
{% trans "Инверторы и генераторы: объекты, обслуживание и плановое ТО" %}
</p>
</div>
<button type="button" class="btn btn-primary" onclick="document.getElementById('power-create-panel').style.display='block'; document.getElementById('power-create-overlay').style.display='block';">
{% trans "Добавить" %}
</button>
</div>
<div style="display:grid; grid-template-columns:repeat(4, minmax(0,1fr)); gap:0.75rem; margin-bottom:1.25rem;">
<div class="stat-card" style="padding:1rem 1.25rem;">
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.375rem;">{% trans "Всего" %}</div>
<div style="font-size:1.5rem; font-weight:500;">{{ power_counts.total }}</div>
</div>
<div class="stat-card" style="padding:1rem 1.25rem;">
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.375rem;">{% trans "Инверторы" %}</div>
<div style="font-size:1.5rem; font-weight:500;">{{ power_counts.inverter }}</div>
</div>
<div class="stat-card" style="padding:1rem 1.25rem;">
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.375rem;">{% trans "Генераторы" %}</div>
<div style="font-size:1.5rem; font-weight:500;">{{ power_counts.generator }}</div>
</div>
<div class="stat-card" style="padding:1rem 1.25rem;">
<div style="font-size:0.6875rem; color:var(--color-muted-foreground); text-transform:uppercase; letter-spacing:0.05em; margin-bottom:0.375rem;">{% trans "Просрочено ТО" %}</div>
<div style="font-size:1.5rem; font-weight:500; {% if power_counts.overdue %}color:var(--color-destructive);{% endif %}">{{ power_counts.overdue }}</div>
</div>
</div>
{% if power_form_message %}
<div style="background:rgba(22,163,74,0.08); border:1px solid rgba(22,163,74,0.3); border-radius:0.75rem; padding:0.75rem 1rem; margin-bottom:1rem; font-size:0.875rem; color:#16a34a;">{{ power_form_message }}</div>
{% endif %}
{% if power_form_error %}
<div style="background:color-mix(in srgb, var(--color-destructive) 8%, transparent); border:1px solid color-mix(in srgb, var(--color-destructive) 30%, transparent); border-radius:0.75rem; padding:0.75rem 1rem; margin-bottom:1rem; font-size:0.875rem; color:var(--color-destructive);">{{ power_form_error }}</div>
{% endif %}
<form method="get" action="/accounting/" style="display:flex; gap:0.75rem; flex-wrap:wrap; margin-bottom:1rem; align-items:flex-end;">
<input type="hidden" name="tab" value="power">
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Тип" %}</label>
<select name="power_type" class="input" style="min-width:10rem;" onchange="this.form.requestSubmit()">
<option value="">{% trans "Все" %}</option>
{% for id, name in power_asset_types %}
<option value="{{ id }}" {% if power_type_filter == id|stringformat:"s" %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
</div>
<div style="min-width:16rem; flex:1;">
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Объект" %}</label>
<div class="searchable-select" data-placeholder="{% trans 'Аптека / офис...' %}">
<select name="power_location" hidden onchange="this.form.requestSubmit()">
<option value="">{% trans "Все объекты" %}</option>
{% for loc in power_locations %}
<option value="{{ loc.id }}" data-search="{{ loc.search_text }}" {% if power_location_filter == loc.id|stringformat:"s" %}selected{% endif %}>{{ loc }}</option>
{% endfor %}
</select>
</div>
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Статус" %}</label>
<select name="power_status" class="input" style="min-width:10rem;" onchange="this.form.requestSubmit()">
<option value="">{% trans "Все" %}</option>
{% for id, name in power_asset_statuses %}
<option value="{{ id }}" {% if power_status_filter == id|stringformat:"s" %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
</div>
<label style="display:flex; align-items:center; gap:0.375rem; font-size:0.8125rem; padding-bottom:0.35rem; cursor:pointer;">
<input type="checkbox" name="power_due" value="1" {% if power_due_filter %}checked{% endif %} onchange="this.form.requestSubmit()">
{% trans "Только просроченное ТО" %}
</label>
</form>
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); overflow:hidden;">
<div style="overflow-x:auto;">
<table style="width:100%; border-collapse:collapse; font-size:0.875rem;">
<thead>
<tr style="border-bottom:1px solid var(--color-border);">
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "Тип / модель" %}</th>
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "Объект" %}</th>
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "С/н" %}</th>
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "кВт" %}</th>
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "ТО" %}</th>
<th style="padding:0.75rem 1rem; text-align:left; color:var(--color-muted-foreground); font-weight:600; font-size:0.8125rem;">{% trans "Статус" %}</th>
</tr>
</thead>
<tbody>
{% for asset in power_assets_page %}
<tr class="it-row" style="border-bottom:1px solid var(--color-border); cursor:pointer;" onclick="var r=document.getElementById('power-row-{{ asset.id }}'); r.style.display=r.style.display==='none'?'':'none';">
<td style="padding:0.75rem 1rem;">
<strong>{{ asset.asset_type.name }}</strong>
<div style="font-size:0.75rem; color:var(--color-muted-foreground); margin-top:0.125rem;">{{ asset.brand }} {{ asset.model_name }}</div>
</td>
<td style="padding:0.75rem 1rem; color:var(--color-muted-foreground); font-size:0.8125rem;">{{ asset.location }}</td>
<td style="padding:0.75rem 1rem; color:var(--color-muted-foreground); font-size:0.8125rem;">{{ asset.serial_number|default:"—" }}</td>
<td style="padding:0.75rem 1rem;">{{ asset.capacity_kw|default:"—" }}</td>
<td style="padding:0.75rem 1rem; font-size:0.8125rem;">
{% if asset.next_service_at %}
{% if asset.service_overdue %}
<span class="badge badge-danger">{% trans "Просрочено" %} {{ asset.next_service_at|date:"d.m.Y" }}</span>
{% else %}
<span style="color:var(--color-muted-foreground);">{% trans "до" %} {{ asset.next_service_at|date:"d.m.Y" }}</span>
{% endif %}
{% elif asset.last_serviced_at %}
<span style="color:var(--color-muted-foreground);">{% trans "было" %} {{ asset.last_serviced_at|date:"d.m.Y" }}</span>
{% else %}
<span style="color:var(--color-muted-foreground);">—</span>
{% endif %}
</td>
<td style="padding:0.75rem 1rem;"><span class="badge badge-muted">{{ asset.status.name }}</span></td>
</tr>
<tr id="power-row-{{ asset.id }}" style="{% if power_open_asset_id != asset.id %}display:none;{% endif %} background:var(--color-muted); border-bottom:1px solid var(--color-border);">
<td colspan="6" style="padding:1rem;">
<form hx-post="/power-assets/{{ asset.id }}/update/" hx-target="#main-content" hx-indicator="#loader"
style="display:grid; grid-template-columns:repeat(auto-fill,minmax(11rem,1fr)); gap:0.75rem; align-items:end;" onclick="event.stopPropagation();">
{% csrf_token %}
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Тип" %}</label>
<select name="asset_type" class="input" style="width:100%;" required>
{% for id, name in power_asset_types %}
<option value="{{ id }}" {% if id == asset.asset_type_id %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
</div>
<div style="grid-column: span 2;">
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Объект" %}</label>
<div class="searchable-select" data-placeholder="{% trans 'Поиск объекта...' %}">
<select name="location_id" required hidden>
{% for loc in power_locations %}
<option value="{{ loc.id }}" data-search="{{ loc.search_text }}" {% if loc.id == asset.location_id %}selected{% endif %}>{{ loc }}</option>
{% endfor %}
</select>
</div>
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Бренд" %}</label>
<input type="text" name="brand" value="{{ asset.brand }}" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Модель" %}</label>
<input type="text" name="model_name" value="{{ asset.model_name }}" class="input" style="width:100%;" required>
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Серийный №" %}</label>
<input type="text" name="serial_number" value="{{ asset.serial_number }}" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Инв. №" %}</label>
<input type="text" name="inventory_number" value="{{ asset.inventory_number }}" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "кВт" %}</label>
<input type="text" name="capacity_kw" value="{{ asset.capacity_kw|default_if_none:'' }}" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Топливо" %}</label>
<input type="text" name="fuel_type" value="{{ asset.fuel_type }}" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "АКБ кВт·ч" %}</label>
<input type="text" name="battery_capacity_kwh" value="{{ asset.battery_capacity_kwh|default_if_none:'' }}" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Установлен" %}</label>
<input type="date" name="installed_at" value="{{ asset.installed_at|date:'Y-m-d' }}" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Гарантия до" %}</label>
<input type="date" name="warranty_until" value="{{ asset.warranty_until|date:'Y-m-d' }}" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Статус" %}</label>
<select name="status" class="input" style="width:100%;">
{% for id, name in power_asset_statuses %}
<option value="{{ id }}" {% if id == asset.status_id %}selected{% endif %}>{{ name }}</option>
{% endfor %}
</select>
</div>
<div style="grid-column: 1 / -1;">
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Примечания" %}</label>
<input type="text" name="notes" value="{{ asset.notes }}" class="input" style="width:100%;">
</div>
<div>
<button type="submit" class="btn btn-primary">{% trans "Сохранить" %}</button>
</div>
</form>
<div style="margin-top:1.25rem; padding-top:1rem; border-top:1px solid var(--color-border);" onclick="event.stopPropagation();">
<div style="font-size:0.8125rem; font-weight:600; margin-bottom:0.75rem;">{% trans "Журнал обслуживания" %}</div>
<form hx-post="/power-assets/{{ asset.id }}/service/" hx-target="#main-content" hx-indicator="#loader"
style="display:grid; grid-template-columns:repeat(auto-fill,minmax(10rem,1fr)); gap:0.625rem; align-items:end; margin-bottom:1rem;">
{% csrf_token %}
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Дата" %}</label>
<input type="date" name="service_date" value="{{ power_today|date:'Y-m-d' }}" class="input" style="width:100%;" required>
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Вид работ" %}</label>
<select name="kind" class="input" style="width:100%;">
{% for code, label in power_service_kinds %}
<option value="{{ code }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<div style="grid-column: span 2;">
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Что сделано" %}</label>
<input type="text" name="description" class="input" style="width:100%;" required placeholder="{% trans 'Например: замена масла, проверка АКБ…' %}">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Исполнитель" %}</label>
<input type="text" name="performed_by_name" class="input" style="width:100%;" placeholder="{% trans 'ФИО / подрядчик' %}">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Моточасы" %}</label>
<input type="text" name="engine_hours" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Стоимость" %}</label>
<input type="text" name="cost" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.75rem; color:var(--color-muted-foreground); margin-bottom:0.25rem;">{% trans "Следующее ТО" %}</label>
<input type="date" name="next_service_at" class="input" style="width:100%;">
</div>
<div>
<button type="submit" class="btn btn-secondary" style="border:1px solid var(--color-border);">{% trans "Записать ТО" %}</button>
</div>
</form>
{% if asset.service_logs.all %}
<ul style="list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:0.5rem;">
{% for log in asset.service_logs.all %}
{% if forloop.counter <= 8 %}
<li style="padding:0.625rem 0.75rem; background:var(--color-card); border:1px solid var(--color-border); border-radius:var(--radius); font-size:0.8125rem;">
<div style="display:flex; justify-content:space-between; gap:0.75rem; flex-wrap:wrap;">
<strong>{{ log.service_date|date:"d.m.Y" }} · {{ log.get_kind_display }}</strong>
<span style="color:var(--color-muted-foreground);">
{% if log.performed_by_name %}{{ log.performed_by_name }}{% elif log.performed_by %}{{ log.performed_by.get_full_name }}{% endif %}
{% if log.engine_hours %} · {{ log.engine_hours }} {% trans "м/ч" %}{% endif %}
{% if log.next_service_at %} · {% trans "след." %} {{ log.next_service_at|date:"d.m.Y" }}{% endif %}
</span>
</div>
<div style="margin-top:0.25rem;">{{ log.description }}</div>
</li>
{% endif %}
{% endfor %}
</ul>
{% else %}
<p style="font-size:0.8125rem; color:var(--color-muted-foreground); margin:0;">{% trans "Записей обслуживания пока нет." %}</p>
{% endif %}
</div>
</td>
</tr>
{% empty %}
<tr><td colspan="6" style="padding:2rem; text-align:center; color:var(--color-muted-foreground);">{% trans "Пока нет записей — добавьте инвертор или генератор" %}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
{% include "partials/pagination.html" with page=power_assets_page base_url="/accounting/?tab=power" page_param="power_page" size_param="power_per_page" item_label="единиц" %}
</div>
<div id="power-create-overlay" onclick="this.style.display='none'; document.getElementById('power-create-panel').style.display='none';" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:1000;"></div>
<div id="power-create-panel" style="display:none; position:fixed; top:0; right:0; height:100vh; width:28rem; max-width:92vw; background:var(--color-card); box-shadow:-8px 0 24px rgba(0,0,0,0.18); z-index:1001; overflow-y:auto;">
<div style="padding:1rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; justify-content:space-between; align-items:center; position:sticky; top:0; background:var(--color-card);">
<span style="font-weight:600;">{% trans "Новое энергооборудование" %}</span>
<button type="button" class="btn btn-ghost btn-icon" onclick="document.getElementById('power-create-panel').style.display='none'; document.getElementById('power-create-overlay').style.display='none';" aria-label="{% trans 'Закрыть' %}">×</button>
</div>
<form hx-post="/power-assets/create/" hx-target="#main-content" hx-indicator="#loader"
style="padding:1.25rem; display:flex; flex-direction:column; gap:0.875rem;">
{% csrf_token %}
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Тип" %} *</label>
<select name="asset_type" class="input" style="width:100%;" required>
<option value="">{% trans "— выберите —" %}</option>
{% for id, name in power_asset_types %}
<option value="{{ id }}">{{ name }}</option>
{% endfor %}
</select>
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Объект (аптека / офис)" %} *</label>
<div class="searchable-select" data-placeholder="{% trans 'Поиск: А3, адрес...' %}">
<select name="location_id" required hidden>
<option value="">{% trans "— выберите объект —" %}</option>
{% for loc in power_locations %}
<option value="{{ loc.id }}" data-search="{{ loc.search_text }}">{{ loc }}</option>
{% endfor %}
</select>
</div>
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.75rem;">
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Бренд" %}</label>
<input type="text" name="brand" class="input" style="width:100%;" placeholder="Deye, Victron…">
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Модель" %} *</label>
<input type="text" name="model_name" class="input" style="width:100%;" required>
</div>
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.75rem;">
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Серийный №" %}</label>
<input type="text" name="serial_number" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Инв. №" %}</label>
<input type="text" name="inventory_number" class="input" style="width:100%;">
</div>
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.75rem;">
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Мощность, кВт" %}</label>
<input type="text" name="capacity_kw" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Топливо" %}</label>
<input type="text" name="fuel_type" class="input" style="width:100%;" placeholder="{% trans 'для генератора' %}">
</div>
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Ёмкость АКБ, кВт·ч" %}</label>
<input type="text" name="battery_capacity_kwh" class="input" style="width:100%;" placeholder="{% trans 'для инвертора' %}">
</div>
<div style="display:grid; grid-template-columns:1fr 1fr; gap:0.75rem;">
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Дата установки" %}</label>
<input type="date" name="installed_at" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Гарантия до" %}</label>
<input type="date" name="warranty_until" class="input" style="width:100%;">
</div>
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Статус" %}</label>
<select name="status" class="input" style="width:100%;">
{% for id, name in power_asset_statuses %}
<option value="{{ id }}">{{ name }}</option>
{% endfor %}
</select>
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; margin-bottom:0.375rem;">{% trans "Примечания" %}</label>
<textarea name="notes" class="input" style="width:100%; min-height:4rem;"></textarea>
</div>
<button type="submit" class="btn btn-primary">{% trans "Сохранить" %}</button>
</form>
</div>
<script>if (typeof initSearchableSelects === 'function') initSearchableSelects(document);</script>
</div>