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

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

334 lines
21 KiB
HTML

{% load i18n %}
{% if not accounting_embed %}<div class="page-content">{% endif %}
{% if not accounting_embed %}
<div style="margin-bottom:1.5rem; display:flex; align-items:flex-start; justify-content:space-between; gap:1rem; flex-wrap:wrap;">
<div>
<h1 style="font-size:1.25rem; font-weight:500; color:var(--color-foreground);">{% trans "РРО / ПРРО" %}</h1>
<p style="font-size:0.875rem; color:var(--color-muted-foreground); margin-top:0.25rem;">
{% if is_staff %}{% trans "Реестр кассовых аппаратов по аптекам" %}{% else %}{% trans "Кассовые аппараты вашей аптеки" %}{% endif %}
</p>
</div>
{% if can_edit_rro %}
<button type="button" class="btn btn-primary" style="font-size:0.8125rem; white-space:nowrap;" onclick="rroOpenCreate()">
{% trans "Добавить" %}
</button>
{% endif %}
</div>
{% elif can_edit_rro %}
<div style="margin-bottom:1rem; display:flex; justify-content:flex-end;">
<button type="button" class="btn btn-primary" style="font-size:0.8125rem; white-space:nowrap;" onclick="rroOpenCreate()">
{% trans "Добавить" %}
</button>
</div>
{% endif %}
{% if rro_form_message %}
<div style="margin-bottom:1rem; padding:0.75rem 1rem; border-radius:0.5rem; border:1px solid hsl(152 40% 70%); background:color-mix(in srgb, var(--color-success) 12%, var(--color-card)); color:var(--color-foreground); font-size:0.875rem;">
{{ rro_form_message }}
</div>
{% endif %}
<form method="get" hx-get="{{ accounting_base_url|default:'/rro/' }}" hx-target="#main-content" hx-push-url="true" hx-indicator="#loader"
style="display:flex; align-items:center; gap:0.5rem; margin-bottom:1rem; flex-wrap:wrap;">
<input type="text" name="q" value="{{ filter_q }}" class="input" style="width:14rem;"
placeholder="{% trans 'ФН, серийник, модель…' %}">
{% if is_staff %}
<select name="organization" class="input" style="width:auto;" onchange="this.form.requestSubmit()">
<option value="">{% trans "Все организации" %}</option>
{% for org in organizations %}
<option value="{{ org.pk }}" {% if filter_org == org.pk|stringformat:"s" %}selected{% endif %}>{{ org.name }}</option>
{% endfor %}
</select>
<select name="pharmacy" class="input" style="width:auto;" onchange="this.form.requestSubmit()">
<option value="">{% trans "Все аптеки" %}</option>
{% for p in all_pharmacies %}
<option value="{{ p.pk }}" {% if filter_pharmacy == p.pk|stringformat:"s" %}selected{% endif %}>{{ p.organization.name }} №{{ p.pharmacy_number }}</option>
{% endfor %}
</select>
{% endif %}
<select name="kind" class="input" style="width:auto;" onchange="this.form.requestSubmit()">
<option value="">{% trans "Все типы" %}</option>
{% for value, label in kind_choices %}
<option value="{{ value }}" {% if filter_kind == value %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<select name="status" class="input" style="width:auto;" onchange="this.form.requestSubmit()">
<option value="">{% trans "Все статусы" %}</option>
{% for s in statuses %}
<option value="{{ s.pk }}" {% if filter_status == s.pk|stringformat:"s" %}selected{% endif %}>{{ s.name }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-secondary" style="font-size:0.8125rem;">{% trans "Найти" %}</button>
</form>
<div style="background:var(--color-card); border:1px solid var(--color-border); border-radius:0.75rem; overflow:hidden; box-shadow:0 1px 2px rgb(0 0 0/0.05);">
<div style="overflow-x:auto;">
<table style="width:100%; border-collapse:collapse; font-size:0.8125rem;">
<thead>
<tr style="border-bottom:1px solid var(--color-border); text-align:left;">
<th style="padding:0.75rem 1rem; color:var(--color-muted-foreground); font-weight:500;">{% trans "Аптека" %}</th>
<th style="padding:0.75rem 1rem; color:var(--color-muted-foreground); font-weight:500;">{% trans "Тип" %}</th>
<th style="padding:0.75rem 1rem; color:var(--color-muted-foreground); font-weight:500;">{% trans "Фискальный номер" %}</th>
<th style="padding:0.75rem 1rem; color:var(--color-muted-foreground); font-weight:500;">{% trans "Модель" %}</th>
<th style="padding:0.75rem 1rem; color:var(--color-muted-foreground); font-weight:500;">{% trans "Статус" %}</th>
<th style="padding:0.75rem 1rem; color:var(--color-muted-foreground); font-weight:500;">{% trans "Сертификат до" %}</th>
{% if can_edit_rro %}
<th style="padding:0.75rem 1rem; color:var(--color-muted-foreground); font-weight:500;"></th>
{% endif %}
</tr>
</thead>
<tbody>
{% for reg in registers %}
<tr style="border-bottom:1px solid var(--color-border);">
<td style="padding:0.75rem 1rem;">
<a href="/pharmacies/{{ reg.pharmacy_id }}/" hx-get="/pharmacies/{{ reg.pharmacy_id }}/" hx-target="#main-content" hx-push-url="/pharmacies/{{ reg.pharmacy_id }}/" hx-indicator="#loader"
style="color:var(--color-foreground); text-decoration:none; font-weight:500;">
{{ reg.pharmacy.organization.name }} №{{ reg.pharmacy.pharmacy_number }}
</a>
{% if reg.is_primary %}<span class="badge badge-info" style="margin-left:0.35rem;">{% trans "осн." %}</span>{% endif %}
</td>
<td style="padding:0.75rem 1rem;">{{ reg.get_kind_display }}</td>
<td style="padding:0.75rem 1rem; font-family:ui-monospace,monospace;">{{ reg.fiscal_number }}</td>
<td style="padding:0.75rem 1rem; color:var(--color-muted-foreground);">
{% if reg.vendor %}{{ reg.vendor.name }} {% endif %}{{ reg.model_name|default:"—" }}
</td>
<td style="padding:0.75rem 1rem;">
{% if reg.status.code == 'active' %}<span class="badge badge-success">{{ reg.status.name }}</span>
{% elif reg.status.code == 'repair' %}<span class="badge badge-warning">{{ reg.status.name }}</span>
{% elif reg.status.code == 'decommissioned' %}<span class="badge badge-danger">{{ reg.status.name }}</span>
{% else %}<span class="badge badge-muted">{{ reg.status.name }}</span>{% endif %}
</td>
<td style="padding:0.75rem 1rem; color:var(--color-muted-foreground);">
{% if reg.cert_expires_at %}{{ reg.cert_expires_at|date:"d.m.Y" }}{% else %}—{% endif %}
</td>
{% if can_edit_rro %}
<td style="padding:0.75rem 1rem; text-align:right;">
<button type="button" class="btn btn-secondary" style="font-size:0.75rem; padding:0.3rem 0.625rem; border:1px solid var(--color-border);"
onclick="rroOpenEdit({{ reg.pk }})">{% trans "Изменить" %}</button>
</td>
{% endif %}
</tr>
{% empty %}
<tr>
<td colspan="{% if can_edit_rro %}7{% else %}6{% endif %}" style="padding:2rem; text-align:center; color:var(--color-muted-foreground);">
{% trans "Кассовые аппараты не найдены" %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% if can_edit_rro %}
<div id="rro-form-overlay" onclick="rroCloseForm()" style="display:none; position:fixed; inset:0; background:rgba(0,0,0,0.4); z-index:1000;"></div>
<div id="rro-form-panel" style="position:fixed; top:0; right:0; height:100vh; width:28rem; max-width:96vw; background:var(--color-card); box-shadow:-8px 0 24px rgba(0,0,0,0.18); z-index:1001; transform:translateX(100%); transition:transform 250ms ease; overflow-y:auto;">
<form id="rro-register-form" hx-post="/rro/create/" hx-target="#main-content" hx-indicator="#loader, find .btn-label-loading" hx-disabled-elt="find button[type=submit]">
{% csrf_token %}
<div style="padding:1rem 1.25rem; border-bottom:1px solid var(--color-border); display:flex; align-items:center; justify-content:space-between; position:sticky; top:0; background:var(--color-card); z-index:1;">
<span id="rro-form-title" style="font-size:0.9375rem; font-weight:600; color:var(--color-foreground);">{% trans "Новый кассовый аппарат" %}</span>
<button type="button" onclick="rroCloseForm()" class="btn btn-ghost btn-icon" aria-label="{% trans 'Закрыть' %}">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 6 6 18M6 6l12 12"/></svg>
</button>
</div>
<div id="rro-form-errors" style="{% if rro_form_errors %}{% else %}display:none;{% endif %} margin:1rem 1.25rem 0; padding:0.75rem 1rem; border-radius:0.5rem; background:hsl(0 70% 96%); border:1px solid hsl(0 70% 85%); color:hsl(0 55% 35%); font-size:0.8125rem;">
{% if rro_form_errors %}
<ul style="margin:0; padding-left:1.1rem;">
{% for field, messages in rro_form_errors.items %}
{% for msg in messages %}
<li>{{ msg }}</li>
{% endfor %}
{% endfor %}
</ul>
{% endif %}
</div>
<div style="padding:1.25rem; display:flex; flex-direction:column; gap:1rem;">
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Аптека" %} <span style="color:var(--color-destructive);">*</span></label>
<select name="pharmacy" id="rro-field-pharmacy" required class="input" style="width:100%;">
<option value="">—</option>
{% for p in all_pharmacies %}
<option value="{{ p.pk }}">{{ p.organization.name }} №{{ p.pharmacy_number }}</option>
{% endfor %}
</select>
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Тип" %} <span style="color:var(--color-destructive);">*</span></label>
<select name="kind" id="rro-field-kind" required class="input" style="width:100%;" onchange="rroKindToggle(this.value)">
{% for value, label in kind_choices %}
<option value="{{ value }}">{{ label }}</option>
{% endfor %}
</select>
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Фискальный номер" %} <span style="color:var(--color-destructive);">*</span></label>
<input type="text" name="fiscal_number" id="rro-field-fiscal" required class="input" style="width:100%;" autocomplete="off">
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Серийный номер" %}</label>
<input type="text" name="serial_number" id="rro-field-serial" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Производитель" %}</label>
<select name="vendor" id="rro-field-vendor" class="input" style="width:100%;">
<option value="">—</option>
{% for v in vendors %}
<option value="{{ v.pk }}">{{ v.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Модель" %}</label>
<input type="text" name="model_name" id="rro-field-model" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Статус" %} <span style="color:var(--color-destructive);">*</span></label>
<select name="status" id="rro-field-status" required class="input" style="width:100%;">
{% for s in statuses %}
<option value="{{ s.pk }}" {% if s.code == 'active' %}selected{% endif %}>{{ s.name }}</option>
{% endfor %}
</select>
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Дата регистрации в ГНС" %}</label>
<input type="date" name="registered_at" id="rro-field-registered" class="input" style="width:100%;">
</div>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Срок сертификата / ключа" %}</label>
<input type="date" name="cert_expires_at" id="rro-field-cert" class="input" style="width:100%;">
</div>
<div id="rro-provider-wrap">
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Провайдер ПРРО" %}</label>
<input type="text" name="provider" id="rro-field-provider" class="input" style="width:100%;" placeholder="checkbox, vchasno…">
</div>
<label style="display:flex; align-items:center; gap:0.5rem; font-size:0.8125rem; color:var(--color-foreground); cursor:pointer;">
<input type="checkbox" name="is_primary" id="rro-field-primary" value="1">
{% trans "Основной на аптеке" %}
</label>
<div>
<label style="display:block; font-size:0.8125rem; font-weight:500; color:var(--color-foreground); margin-bottom:0.375rem;">{% trans "Комментарий" %}</label>
<textarea name="notes" id="rro-field-notes" class="input" rows="3" style="width:100%; resize:vertical;"></textarea>
</div>
</div>
<div style="padding:1rem 1.25rem; border-top:1px solid var(--color-border); display:flex; gap:0.5rem; justify-content:flex-end; position:sticky; bottom:0; background:var(--color-card);">
<button type="button" class="btn btn-secondary" style="font-size:0.8125rem; border:1px solid var(--color-border);" onclick="rroCloseForm()">{% trans "Отмена" %}</button>
<button type="submit" class="btn btn-primary" style="font-size:0.8125rem;">
<span>{% trans "Сохранить" %}</span>
<span class="btn-label-loading htmx-indicator">…</span>
</button>
</div>
</form>
</div>
<script type="application/json" id="rro-registers-json">{{ registers_json|safe }}</script>
{% if rro_form_data_json %}<script type="application/json" id="rro-form-data-json">{{ rro_form_data_json|safe }}</script>{% endif %}
{% endif %}
{% if not accounting_embed %}</div>{% endif %}
{% if can_edit_rro %}
<script>
(function () {
var TITLE_CREATE = '{% filter escapejs %}{% trans "Новый кассовый аппарат" %}{% endfilter %}';
var TITLE_EDIT = '{% filter escapejs %}{% trans "Редактирование РРО / ПРРО" %}{% endfilter %}';
function panel() { return document.getElementById('rro-form-panel'); }
function overlay() { return document.getElementById('rro-form-overlay'); }
function formEl() { return document.getElementById('rro-register-form'); }
window.rroKindToggle = function (kind) {
var wrap = document.getElementById('rro-provider-wrap');
if (wrap) wrap.style.display = (kind === 'prro') ? '' : 'none';
};
window.rroCloseForm = function () {
var p = panel(), o = overlay();
if (p) p.style.transform = 'translateX(100%)';
if (o) o.style.display = 'none';
};
function openPanel() {
var p = panel(), o = overlay();
if (o) o.style.display = 'block';
requestAnimationFrame(function () {
if (p) p.style.transform = 'translateX(0)';
});
}
function setVal(id, value) {
var el = document.getElementById(id);
if (!el) return;
if (el.type === 'checkbox') el.checked = !!value;
else el.value = value == null ? '' : String(value);
}
function applyFormData(data) {
if (!data) return;
setVal('rro-field-pharmacy', data.pharmacy || data.pharmacy_id || '');
setVal('rro-field-kind', data.kind || 'prro');
setVal('rro-field-fiscal', data.fiscal_number || '');
setVal('rro-field-serial', data.serial_number || '');
setVal('rro-field-vendor', data.vendor || data.vendor_id || '');
setVal('rro-field-model', data.model_name || '');
setVal('rro-field-status', data.status || data.status_id || '');
setVal('rro-field-registered', data.registered_at || '');
setVal('rro-field-cert', data.cert_expires_at || '');
setVal('rro-field-provider', data.provider || '');
setVal('rro-field-primary', !!data.is_primary);
setVal('rro-field-notes', data.notes || '');
rroKindToggle(document.getElementById('rro-field-kind').value);
}
function parseRegisters() {
var el = document.getElementById('rro-registers-json');
if (!el) return [];
try { return JSON.parse(el.textContent); } catch (e) { return []; }
}
window.rroOpenCreate = function (prefillPharmacy) {
var f = formEl();
if (!f) return;
f.setAttribute('hx-post', '/rro/create/');
if (window.htmx) htmx.process(f);
document.getElementById('rro-form-title').textContent = TITLE_CREATE;
f.reset();
var activeOpt = document.querySelector('#rro-field-status option[value]') ;
// keep default selected=active from markup
setVal('rro-field-kind', 'prro');
rroKindToggle('prro');
if (prefillPharmacy) setVal('rro-field-pharmacy', String(prefillPharmacy));
openPanel();
};
window.rroOpenEdit = function (pk) {
var f = formEl();
if (!f) return;
var data = parseRegisters().find(function (r) { return r.id === pk; });
if (!data) return;
f.setAttribute('hx-post', '/rro/' + pk + '/update/');
if (window.htmx) htmx.process(f);
document.getElementById('rro-form-title').textContent = TITLE_EDIT;
applyFormData(data);
openPanel();
};
{% if show_create_panel %}
rroOpenCreate({% if prefill_pharmacy %}{{ prefill_pharmacy }}{% else %}null{% endif %});
{% endif %}
{% if rro_edit_id %}
rroOpenEdit({{ rro_edit_id }});
{% endif %}
{% if rro_form_data_json %}
(function () {
try {
applyFormData(JSON.parse(document.getElementById('rro-form-data-json').textContent));
} catch (e) {}
})();
{% endif %}
})();
</script>
{% endif %}