brewman/brewman/brewman/routers/fingerprint.py

190 lines
5.8 KiB
Python

import csv
import uuid
from datetime import date, datetime, time, timedelta
from io import StringIO
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from sqlalchemy import and_, bindparam, exists, select
from sqlalchemy.dialects.postgresql import insert as pg_insert
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import Session
from ..core.security import get_current_active_user as get_user
from ..db.session import SessionLocal
from ..models.master import Employee
from ..models.voucher import Fingerprint
from ..routers import get_lock_info
from ..schemas.auth import UserToken
router = APIRouter()
# Dependency
def get_db() -> Session:
try:
db = SessionLocal()
yield db
finally:
db.close()
@router.post("")
def upload_prints(
db: Session = Depends(get_db),
fingerprints: UploadFile = File(None),
user: UserToken = Depends(get_user),
):
try:
start, finish = get_lock_info(db)
employees = {}
for id_, code in db.query(Employee.id, Employee.code).all():
employees[code] = id_
file_data = read_file(fingerprints)
prints = [d for d in fp(file_data, employees) if start <= d["date"] <= finish]
paged_data = [prints[i : i + 100] for i in range(0, len(prints), 100)]
for i, page in enumerate(paged_data):
print(f"Processing page {i} of {len(paged_data)}")
db.execute(get_query(9.4), page)
db.commit()
return {}
except SQLAlchemyError as e:
db.rollback()
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=str(e),
)
except Exception:
db.rollback()
raise
def get_query(version):
if version == 9.5:
return (
pg_insert(Fingerprint)
.values(
{
"FingerprintID": bindparam("id"),
"EmployeeID": bindparam("employee_id"),
"Date": bindparam("date"),
}
)
.on_conflict_do_nothing()
)
else:
sel = select(
[bindparam("id"), bindparam("employee_id"), bindparam("date")]
).where(
~exists([Fingerprint.id]).where(
and_(
Fingerprint.employee_id == bindparam("employee_id"),
Fingerprint.date == bindparam("date"),
)
)
)
return Fingerprint.__table__.insert().from_select(
[Fingerprint.id, Fingerprint.employee_id, Fingerprint.date], sel
)
def read_file(input_file: UploadFile):
input_file.seek(0)
output = bytearray()
while 1:
data = input_file.read(2 << 16)
if not data:
break
output.extend(data)
if output[0:3] == b"\xef\xbb\xbf":
encoding = "utf-8"
elif output[0:2] == b"\xfe\xff" or output[0:2] == b"\xff\xfe":
encoding = "utf-16"
else:
encoding = "ascii"
# raise ValidationError("The encoding of the attendance file is not correct")
return StringIO(output.decode(encoding))
def fp(file_data, employees):
fingerprints = []
reader = csv.reader(file_data, delimiter="\t")
header = next(reader, None)
employee_column = 2
date_column = len(header) - 1
date_format = "%Y/%m/%d %H:%M:%S" if date_column == 6 else "%Y-%m-%d %H:%M:%S"
for row in reader:
try:
employee_code = int(row[employee_column]) # EnNo
date = datetime.datetime.strptime(row[date_column], date_format)
if employee_code in employees.keys():
fingerprints.append(
{
"id": uuid.uuid4(),
"employee_id": employees[employee_code],
"date": date,
}
)
except ValueError:
continue
return fingerprints
# # Upsert using subquery
# sel = select([literal(uuid.uuid4()), literal(employee_id), literal(date)]).where(
# ~exists([Fingerprint.id]).where(
# and_(
# Fingerprint.employee_id == employee_id, Fingerprint.date == date
# )
# )
# )
# return Fingerprint.__table__.insert().from_select([Fingerprint.id, Fingerprint.employee_id, Fingerprint.date], sel)
def get_prints(employee_id: uuid.UUID, date_: date, db: Session):
prints = (
db.query(Fingerprint)
.filter(Fingerprint.employee_id == employee_id)
.filter(Fingerprint.date >= datetime.combine(date_, time(hour=7)))
.filter(
Fingerprint.date < datetime.combine(date_ + timedelta(days=1), time(hour=7))
)
.order_by(Fingerprint.date)
.all()
)
last = None
for i in range(len(prints), 0, -1):
item = prints[i - 1].date
if last is not None and last - item < timedelta(minutes=10):
prints.remove(prints[i - 1])
else:
last = item
if len(prints) == 0:
hours_worked, full_day = "", None
elif len(prints) == 2:
time_worked = prints[1].date - prints[0].date
hours_worked, full_day = working_hours(time_worked)
elif len(prints) == 4:
time_worked = (prints[1].date - prints[0].date) + (
prints[3].date - prints[2].date
)
hours_worked, full_day = working_hours(time_worked)
else:
hours_worked, full_day = "Error", False
return (
", ".join([x.date.strftime("%H:%M") for x in prints]) + " ",
hours_worked,
full_day,
)
def working_hours(delta: timedelta):
minutes = (delta.seconds // 60) % 60
minutes = int(5 * round(float(minutes) / 5))
hours = delta.seconds // 3600
hours_worked = str(hours).zfill(2) + ":" + str(minutes).zfill(2)
return hours_worked, delta.seconds >= 60 * 60 * 9 # 9hrs