07b4c69d14
Co-authored-by: Cursor <cursoragent@cursor.com>
353 lines
13 KiB
Python
353 lines
13 KiB
Python
"""Views портала для локаций / аптек."""
|
|
from __future__ import annotations
|
|
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.http import HttpResponse, HttpResponseForbidden
|
|
from django.shortcuts import get_object_or_404, render
|
|
from django.views.decorators.http import require_GET, require_POST
|
|
|
|
from core.portal_access import section_required
|
|
from core.utils.htmx import render_page
|
|
|
|
from core.location_access import accessible_pharmacies
|
|
|
|
from .forms import PortalPharmacyEditForm, PortalVendorForm
|
|
from .models import Location, Pharmacy, pharmacy_card_flags, user_can_edit_pharmacy
|
|
|
|
|
|
def user_can_manage_vendors(user) -> bool:
|
|
"""IT-раздел или staff — могут вести справочник сервисов/поставщиков."""
|
|
if not user or not getattr(user, 'is_authenticated', False):
|
|
return False
|
|
if getattr(user, 'is_staff', False) or getattr(user, 'is_superuser', False):
|
|
return True
|
|
from core.portal_access import user_can_access
|
|
return user_can_access(user, 'it_manager')
|
|
|
|
|
|
def _vendor_form_errors(form) -> str:
|
|
errors = []
|
|
for field, msgs in form.errors.items():
|
|
label = form.fields[field].label if field in form.fields else field
|
|
for msg in msgs:
|
|
errors.append(f'{label}: {msg}' if field != '__all__' else msg)
|
|
return ' '.join(errors) or 'Не удалось сохранить.'
|
|
|
|
|
|
def _dislocation_vendors_response(request, *, message=None, error=None, form=None):
|
|
from core.views import dislocation_view
|
|
|
|
request._dislo_tab = 'vendors'
|
|
if message:
|
|
request._dislo_vendor_message = message
|
|
if error:
|
|
request._dislo_vendor_error = error
|
|
if form is not None:
|
|
request._dislo_vendor_form = form
|
|
return dislocation_view(request)
|
|
|
|
|
|
def _pharmacy_detail_context(request, pharmacy, *, form=None, edit_mode=False,
|
|
form_message=None, form_error=None):
|
|
"""Контекст карточки аптеки (общий для GET/POST)."""
|
|
from apps.employees.models import Employee
|
|
from apps.it_manager.models import Equipment, InternetContract
|
|
from apps.rro.models import CashRegister
|
|
from apps.utilities.models import UtilityReading
|
|
|
|
flags = pharmacy_card_flags(request.user, pharmacy)
|
|
|
|
staff = []
|
|
if flags['can_view_staff']:
|
|
staff = list(
|
|
Employee.objects.filter(user_pharmacy=pharmacy, is_active=True)
|
|
.select_related('post').order_by('last_name', 'first_name')
|
|
)
|
|
|
|
equipment = []
|
|
if flags['can_view_equipment']:
|
|
equipment = list(
|
|
Equipment.objects.filter(location=pharmacy)
|
|
.select_related(
|
|
'equipment_model__equipment_type', 'equipment_model__brand',
|
|
'status', 'assigned_to',
|
|
)
|
|
.order_by('equipment_model__equipment_type__name', 'equipment_model__name')
|
|
)
|
|
|
|
internet_contracts = []
|
|
if flags['can_view_internet']:
|
|
internet_contracts = list(
|
|
InternetContract.objects.filter(location=pharmacy)
|
|
.select_related('provider', 'service_type', 'connection_type', 'status')
|
|
.order_by('-start_date')
|
|
)
|
|
|
|
cash_registers = []
|
|
if flags['can_view_rro']:
|
|
cash_registers = list(
|
|
CashRegister.objects.filter(pharmacy=pharmacy)
|
|
.select_related('vendor', 'status')
|
|
.order_by('-is_primary', 'kind', 'fiscal_number')
|
|
)
|
|
|
|
utility_readings = []
|
|
if flags['can_view_utilities']:
|
|
from apps.utilities.helpers import attach_reading_display
|
|
utility_readings = list(
|
|
UtilityReading.objects.filter(pharmacy=pharmacy)
|
|
.select_related('utility_type', 'submitted_by')
|
|
.order_by('-reading_date')[:50]
|
|
)
|
|
attach_reading_display(utility_readings)
|
|
|
|
can_edit = flags['can_edit']
|
|
if edit_mode and can_edit and form is None:
|
|
form = PortalPharmacyEditForm(instance=pharmacy, user=request.user)
|
|
|
|
return {
|
|
'pharmacy': pharmacy,
|
|
'staff': staff,
|
|
'staff_count': len(staff),
|
|
'phones': list(pharmacy.phones.filter(is_active=True)),
|
|
'equipment': equipment,
|
|
'internet_contracts': internet_contracts,
|
|
'cash_registers': cash_registers,
|
|
'utility_readings': utility_readings,
|
|
**flags,
|
|
'can_view_ops': any(flags[f'can_view_{s}'] for s in (
|
|
'staff', 'equipment', 'internet', 'rro', 'utilities', 'documents',
|
|
)),
|
|
'pharmacy_edit_mode': bool(edit_mode and can_edit),
|
|
'pharmacy_edit_form': form if (edit_mode and can_edit) else None,
|
|
'pharmacy_form_message': form_message,
|
|
'pharmacy_form_error': form_error,
|
|
}
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def pharmacy_update_view(request, pk):
|
|
pharmacy = get_object_or_404(
|
|
accessible_pharmacies(request.user).select_related(
|
|
'organization', 'location_region', 'head_pharmacist__post',
|
|
'responsible_manager', 'curator',
|
|
).prefetch_related('phones'),
|
|
pk=pk,
|
|
)
|
|
if not user_can_edit_pharmacy(request.user, pharmacy):
|
|
return HttpResponseForbidden()
|
|
|
|
from_list = request.POST.get('source') == 'list'
|
|
form = PortalPharmacyEditForm(request.POST, instance=pharmacy, user=request.user)
|
|
if form.is_valid():
|
|
pharmacy = form.save(commit=False)
|
|
pharmacy._history_user = request.user
|
|
pharmacy.location_type = 'pharmacy'
|
|
pharmacy.save()
|
|
form.save_m2m()
|
|
pharmacy.refresh_from_db()
|
|
if from_list:
|
|
from core.views import pharmacies_page_context
|
|
return render_page(request, 'pharmacies.html', pharmacies_page_context(request))
|
|
context = _pharmacy_detail_context(
|
|
request, pharmacy,
|
|
form_message='Изменения сохранены.',
|
|
)
|
|
return render_page(request, 'pharmacy-detail.html', context)
|
|
|
|
errors = []
|
|
for field, msgs in form.errors.items():
|
|
label = form.fields[field].label if field in form.fields else field
|
|
for msg in msgs:
|
|
errors.append(f'{label}: {msg}' if field != '__all__' else msg)
|
|
form_error = ' '.join(errors) or 'Не удалось сохранить.'
|
|
if from_list:
|
|
from core.views import pharmacies_page_context
|
|
return render_page(request, 'pharmacies.html', pharmacies_page_context(
|
|
request,
|
|
pharmacy_modal=pharmacy,
|
|
pharmacy_edit_form=form,
|
|
pharmacy_form_error=form_error,
|
|
pharmacy_modal_tab=_pharmacy_modal_tab(form),
|
|
can_edit=True,
|
|
))
|
|
|
|
context = _pharmacy_detail_context(
|
|
request, pharmacy,
|
|
form=form, edit_mode=True,
|
|
form_error=form_error,
|
|
)
|
|
return render_page(request, 'pharmacy-detail.html', context)
|
|
|
|
|
|
def _pharmacy_modal_tab(form) -> str:
|
|
names = set(getattr(form, 'errors', {}) or ())
|
|
if names & {
|
|
'location', 'street', 'house_number', 'building', 'apartment', 'location_region',
|
|
}:
|
|
return 'address'
|
|
if names & {
|
|
'license_number', 'license_expiry_date',
|
|
'head_pharmacist', 'responsible_manager', 'curator',
|
|
}:
|
|
return 'people'
|
|
return 'main'
|
|
|
|
|
|
def _pharmacy_for_portal(request, pk):
|
|
return get_object_or_404(
|
|
accessible_pharmacies(request.user).select_related(
|
|
'organization', 'location_region', 'head_pharmacist__post',
|
|
'responsible_manager', 'curator',
|
|
).prefetch_related('phones'),
|
|
pk=pk,
|
|
)
|
|
|
|
|
|
@login_required
|
|
@section_required('pharmacies')
|
|
def pharmacy_modal_view(request, pk):
|
|
"""Popup редактирования/просмотра аптеки со списка /pharmacies/."""
|
|
pharmacy = _pharmacy_for_portal(request, pk)
|
|
can_edit = user_can_edit_pharmacy(request.user, pharmacy)
|
|
form = PortalPharmacyEditForm(instance=pharmacy, user=request.user) if can_edit else None
|
|
return render(request, 'partials/pharmacy-edit-modal.html', {
|
|
'pharmacy': pharmacy,
|
|
'can_edit': can_edit,
|
|
'pharmacy_edit_form': form,
|
|
'pharmacy_form_error': None,
|
|
})
|
|
|
|
|
|
@login_required
|
|
@section_required('pharmacies')
|
|
@require_GET
|
|
def pharmacies_export_xlsx_view(request):
|
|
"""Выгрузка доступных пользователю аптек в Excel."""
|
|
from io import BytesIO
|
|
|
|
from django.utils import timezone
|
|
from openpyxl import Workbook
|
|
from openpyxl.styles import Font
|
|
from openpyxl.utils import get_column_letter
|
|
|
|
pharmacies = accessible_pharmacies(request.user).select_related(
|
|
'organization', 'location_region', 'head_pharmacist',
|
|
).order_by('organization__name', 'pharmacy_number')
|
|
|
|
wb = Workbook()
|
|
ws = wb.active
|
|
ws.title = 'Аптеки'
|
|
headers = [
|
|
'№', 'Организация', 'Адрес', 'Регион', 'Заведующий',
|
|
'Фармацевтов', 'Телефон', 'Режим работы', 'Статус',
|
|
'Категория', 'НДС', 'Лицензия',
|
|
]
|
|
ws.append(headers)
|
|
for cell in ws[1]:
|
|
cell.font = Font(bold=True)
|
|
for pharmacy in pharmacies:
|
|
head = ''
|
|
if pharmacy.head_pharmacist_id:
|
|
head = f'{pharmacy.head_pharmacist.last_name} {pharmacy.head_pharmacist.first_name}'.strip()
|
|
ws.append([
|
|
pharmacy.pharmacy_number,
|
|
pharmacy.organization.name if pharmacy.organization_id else '',
|
|
pharmacy.address or '',
|
|
pharmacy.location_region.name if pharmacy.location_region_id else '',
|
|
head,
|
|
pharmacy.pharmacists_count,
|
|
pharmacy.phone or '',
|
|
pharmacy.working_hours or '',
|
|
'Активна' if pharmacy.is_active else 'Неактивна',
|
|
pharmacy.category or '',
|
|
pharmacy.get_vat_category_display() if pharmacy.vat_category else '',
|
|
pharmacy.license_number or '',
|
|
])
|
|
for idx, _header in enumerate(headers, start=1):
|
|
ws.column_dimensions[get_column_letter(idx)].width = 18
|
|
|
|
buf = BytesIO()
|
|
wb.save(buf)
|
|
buf.seek(0)
|
|
filename = f'apteki-{timezone.localdate().isoformat()}.xlsx'
|
|
response = HttpResponse(
|
|
buf.getvalue(),
|
|
content_type='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
)
|
|
response['Content-Disposition'] = f'attachment; filename="{filename}"'
|
|
return response
|
|
|
|
|
|
def _vendor_detail_context(request, vendor, *, form=None, edit_mode=False,
|
|
form_message=None, form_error=None):
|
|
can_edit = user_can_manage_vendors(request.user)
|
|
if edit_mode and can_edit and form is None:
|
|
form = PortalVendorForm(instance=vendor)
|
|
return {
|
|
'vendor': vendor,
|
|
'can_edit_vendor': can_edit,
|
|
'vendor_edit_mode': bool(edit_mode and can_edit),
|
|
'vendor_edit_form': form if (edit_mode and can_edit) else None,
|
|
'vendor_form_message': form_message,
|
|
'vendor_form_error': form_error,
|
|
}
|
|
|
|
|
|
@login_required
|
|
def vendor_detail_view(request, pk):
|
|
vendor = get_object_or_404(
|
|
Location.objects.select_related('location_region'),
|
|
pk=pk, location_type='vendor',
|
|
)
|
|
edit_mode = request.GET.get('edit') == '1'
|
|
if edit_mode and not user_can_manage_vendors(request.user):
|
|
return HttpResponseForbidden()
|
|
context = _vendor_detail_context(request, vendor, edit_mode=edit_mode)
|
|
return render_page(request, 'vendor-detail.html', context)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def vendor_create_view(request):
|
|
if not user_can_manage_vendors(request.user):
|
|
return HttpResponseForbidden()
|
|
|
|
form = PortalVendorForm(request.POST)
|
|
if form.is_valid():
|
|
form.save()
|
|
return _dislocation_vendors_response(request, message='Сервис / поставщик добавлен.')
|
|
return _dislocation_vendors_response(
|
|
request, error=_vendor_form_errors(form), form=form,
|
|
)
|
|
|
|
|
|
@login_required
|
|
@require_POST
|
|
def vendor_update_view(request, pk):
|
|
if not user_can_manage_vendors(request.user):
|
|
return HttpResponseForbidden()
|
|
|
|
vendor = get_object_or_404(Location, pk=pk, location_type='vendor')
|
|
form = PortalVendorForm(request.POST, instance=vendor)
|
|
# Если пришли с карточки — остаёмся на ней
|
|
from_card = request.POST.get('from_card') == '1'
|
|
if form.is_valid():
|
|
vendor = form.save()
|
|
if from_card:
|
|
context = _vendor_detail_context(
|
|
request, vendor, form_message='Изменения сохранены.',
|
|
)
|
|
return render_page(request, 'vendor-detail.html', context)
|
|
return _dislocation_vendors_response(request, message='Изменения сохранены.')
|
|
if from_card:
|
|
context = _vendor_detail_context(
|
|
request, vendor, form=form, edit_mode=True,
|
|
form_error=_vendor_form_errors(form),
|
|
)
|
|
return render_page(request, 'vendor-detail.html', context)
|
|
return _dislocation_vendors_response(
|
|
request, error=_vendor_form_errors(form), form=form,
|
|
)
|