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