4e1d5f221e
Co-authored-by: Cursor <cursoragent@cursor.com>
334 lines
21 KiB
HTML
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-panel" role="dialog" aria-modal="true"
|
|
style="display:none; position:fixed; inset:0; z-index:80; background:rgba(0,0,0,0.4); align-items:flex-start; justify-content:center; padding:1.5rem 1rem; overflow-y:auto;"
|
|
onclick="if(event.target===this) rroCloseForm()">
|
|
<div style="background:var(--color-card); border-radius:0.75rem; border:1px solid var(--color-border); box-shadow:0 20px 60px rgb(0 0 0/0.2); width:100%; max-width:36rem; margin: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>
|
|
</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 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();
|
|
if (p) p.style.display = 'none';
|
|
document.body.style.overflow = '';
|
|
};
|
|
|
|
function openPanel() {
|
|
var p = panel();
|
|
if (p) p.style.display = 'flex';
|
|
document.body.style.overflow = 'hidden';
|
|
}
|
|
|
|
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 %}
|