131cb27d16
Django project: employees, locations, visit_manager, it_manager, mattermost_connector and stub connectors (ad, mail, portal).
95 lines
3.0 KiB
Python
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()
|