Files
ACORE/ext-visit manager/files(1)/attendance_services.py
T
lokses 131cb27d16 Initial commit — ACORE pharmacy network management system
Django project: employees, locations, visit_manager, it_manager,
mattermost_connector and stub connectors (ad, mail, portal).
2026-05-31 14:21:47 +03:00

95 lines
3.0 KiB
Python

# apps/attendance/services.py
"""
Сервисный слой: приём фото, генерация превью, сохранение записи.
"""
import io
from django.core.files.base import ContentFile
from django.utils import timezone
from PIL import Image
from .models import AttendanceLog, WorkShift
THUMB_SIZE = (64, 64)
PHOTO_QUALITY = 75 # JPEG quality для оригинала
THUMB_QUALITY = 60 # JPEG quality для превью в bytea
def _generate_thumb(image_bytes: bytes) -> bytes:
"""Возвращает JPEG-превью THUMB_SIZE в виде bytes."""
img = Image.open(io.BytesIO(image_bytes))
img = img.convert('RGB')
img.thumbnail(THUMB_SIZE, Image.LANCZOS)
buf = io.BytesIO()
img.save(buf, format='JPEG', quality=THUMB_QUALITY, optimize=True)
return buf.getvalue()
def _compress_photo(image_bytes: bytes) -> bytes:
"""Пережимает оригинал до разумного размера (~50-80 КБ)."""
img = Image.open(io.BytesIO(image_bytes))
img = img.convert('RGB')
# Ограничиваем разрешение до 640x480 — достаточно для идентификации
img.thumbnail((640, 480), Image.LANCZOS)
buf = io.BytesIO()
img.save(buf, format='JPEG', quality=PHOTO_QUALITY, optimize=True)
return buf.getvalue()
def register_attendance(
*,
employee_id: int,
location_id: int,
direction: str,
photo_bytes: bytes,
device_id: str = '',
ip_address: str | None = None,
) -> AttendanceLog:
"""
Создаёт запись посещения:
- сжимает оригинал и сохраняет через ImageField (ФС)
- генерирует превью и сохраняет в bytea
- обновляет WorkShift
"""
now = timezone.now()
compressed = _compress_photo(photo_bytes)
thumb = _generate_thumb(photo_bytes)
log = AttendanceLog(
employee_id=employee_id,
location_id=location_id,
direction=direction,
timestamp=now,
device_id=device_id,
ip_address=ip_address,
photo_thumb=thumb,
)
filename = f'{employee_id}_{now.strftime("%Y%m%d_%H%M%S")}.jpg'
log.photo.save(filename, ContentFile(compressed), save=False)
log.save()
_update_work_shift(log)
return log
def _update_work_shift(log: AttendanceLog) -> None:
"""
Создаёт или обновляет WorkShift для пары приход/уход.
"""
shift, _ = WorkShift.objects.get_or_create(
employee_id=log.employee_id,
location_id=log.location_id,
date=log.timestamp.date(),
)
if log.direction == 'in' and shift.log_in_id is None:
shift.log_in = log
shift.arrived_at = log.timestamp
elif log.direction == 'out':
shift.log_out = log
shift.left_at = log.timestamp
if shift.arrived_at:
shift.duration = shift.left_at - shift.arrived_at
shift.save()