import math from datetime import datetime from io import BytesIO from time import time import qrcode from fastapi import APIRouter from reportlab.lib.units import mm from reportlab.lib.utils import ImageReader from reportlab.pdfbase import pdfmetrics from reportlab.pdfbase.ttfonts import TTFont from reportlab.pdfgen import canvas from starlette.responses import StreamingResponse from app import mongo from app.utils.response_util import response router = APIRouter() @router.get("/get-shifts/{active}", tags=[""]) async def get_shifts(active: bool, page: int = 1, items_per_page: int = 10): start_time = time() total_count = await mongo.work_shifts_collection.count_documents({"active": active}) total_pages = math.ceil(total_count / items_per_page) if total_count else 1 skip = (page - 1) * items_per_page raw_shifts = await mongo.work_shifts_collection.find( {"active": active}, {"_id": False} ).skip(skip).limit(items_per_page).to_list(length=items_per_page) shifts = [] for shift in raw_shifts: user = await mongo.users_collection.find_one({"id": shift["user_id"]}, {"_id": False}) started_at = shift.get("start_time") finished_at = shift.get("end_time") total_hours = 0 pause_hours = 0 if started_at and finished_at: total_hours = round((finished_at - started_at) / 1000, 2) for pause in shift.get("pause", []): if pause.get("start") and pause.get("end"): pause_hours += (pause["end"] - pause["start"]) / 1000 pause_hours = round(pause_hours, 2) shifts.append({ "workShift": { "id": shift["id"], "user": user, "startedAt": started_at, "finishedAt": finished_at, }, "totalHours": total_hours, "pauseHours": pause_hours, }) return response({ "shifts": shifts, "paginationInfo": { "totalPages": total_pages }, "ok": True }, start_time=start_time) @router.get("/generate-qr-code/{user_id}", tags=[""]) async def generate_qr_code(user_id: int): user = await mongo.users_collection.find_one({"id": user_id}, {"_id": False}) full_name = f'{user["secondName"]} {user["firstName"]}' position = user.get("position", {}).get("name", "") width_mm, height_mm = 58, 40 width_pt, height_pt = width_mm * mm, height_mm * mm buffer = BytesIO() pdf = canvas.Canvas(buffer, pagesize=(width_pt, height_pt)) pdf.setTitle("User QR") pdfmetrics.registerFont(TTFont('Arial', 'assets/arial.ttf')) pdfmetrics.registerFont(TTFont('Arial Bold', 'assets/arial_bold.ttf')) qr_img = qrcode.make(str(user_id)).convert("RGB") qr_width_mm = 30 qr_height_mm = 30 qr_x = (width_mm - qr_width_mm) / 2 * mm qr_y = 2 * mm pdf.drawImage(ImageReader(qr_img), qr_x, qr_y, qr_width_mm * mm, qr_height_mm * mm) text_y = qr_y + qr_height_mm * mm + 4 * mm pdf.setFont("Arial Bold", 9) pdf.drawCentredString(width_pt / 2, text_y, full_name) if position: text_y -= 4 * mm pdf.setFont("Arial", 8) pdf.drawCentredString(width_pt / 2, text_y, position) pdf.showPage() pdf.save() buffer.seek(0) return StreamingResponse(buffer, media_type="application/pdf") @router.post("/start-shift/{user_id}", tags=[""]) async def start_shift(user_id: int): start_time = time() new_id = await mongo.get_next_id(mongo.work_shifts_collection) shift = { "id": new_id, "user_id": user_id, "active": True, "start_time": get_time_millis(), "pause": [], "end_time": None } await mongo.work_shifts_collection.insert_one(shift) return response({ "message": "Смена начата", "ok": True }, start_time=start_time) @router.post("/finish-shift/{user_id}", tags=[""]) async def finish_shift(user_id: int): start_time = time() work_shift = await mongo.work_shifts_collection.find_one({ "user_id": user_id, "active": True }, {"_id": False}) if not work_shift: return response({ "message": "Активная смена не найдена", "ok": False }, start_time=start_time) end_time = get_time_millis() await mongo.work_shifts_collection.update_one( {"id": work_shift["id"]}, {"$set": { "active": False, "end_time": end_time }} ) await add_time_tracking_record(work_shift, end_time) return response({ "message": "Смена завершена", "ok": True }, start_time=start_time) @router.post("/finish-shift-by-id/{shift_id}", tags=[""]) async def finish_shift_by_id(shift_id: int): start_time = time() work_shift = await mongo.work_shifts_collection.find_one({"id": shift_id}, {"_id": False}) if not work_shift: return response({ "message": "Активная смена не найдена", "ok": False }, start_time=start_time) end_time = get_time_millis() await mongo.work_shifts_collection.update_one( {"id": work_shift["id"]}, {"$set": { "active": False, "end_time": end_time }} ) await add_time_tracking_record(work_shift, end_time) return response({ "message": "Смена завершена", "ok": True }, start_time=start_time) @router.delete("/delete-shift/{shift_id}", tags=[""]) async def delete_shift(shift_id: int): start_time = time() await mongo.work_shifts_collection.delete_one({"id": shift_id}) return response({ "message": "Смена удалена", "ok": True }, start_time=start_time) @router.post("/pause/start/{shift_id}", tags=[""]) async def start_pause_by_shift_id(shift_id: int): start_time = time() await mongo.work_shifts_collection.update_one( {"id": shift_id}, {"$push": {"pause": {"start": get_time_millis(), "end": None}}} ) return response({ "message": "Пауза начата", "ok": True }, start_time=start_time) @router.post("/pause/start/for-user/{user_id}", tags=[""]) async def start_pause_by_user_id(user_id: int): start_time = time() await mongo.work_shifts_collection.update_one( {"user_id": user_id, "active": True}, {"$push": {"pause": {"start": get_time_millis(), "end": None}}} ) return response({ "message": "Пауза начата для пользователя", "ok": True }, start_time=start_time) @router.post("/pause/finish/{shift_id}", tags=[""]) async def finish_pause_by_shift_id(shift_id: int): start_time = time() await mongo.work_shifts_collection.update_one( {"id": shift_id, "pause.end": None}, {"$set": {"pause.$.end": get_time_millis()}} ) return response({ "message": "Пауза завершена", "ok": True }, start_time=start_time) @router.post("/pause/finish/for-user/{shift_id}", tags=[""]) async def finish_pause_by_user_id(user_id: int): start_time = time() await mongo.work_shifts_collection.update_one( {"user_id": user_id, "active": True, "pause.end": None}, {"$set": {"pause.$.end": get_time_millis()}} ) return response({ "message": "Пауза завершена для пользователя", "ok": True }, start_time=start_time) async def add_time_tracking_record(work_shift: dict, end_time: int): user_id = work_shift["user_id"] start_time = work_shift["start_time"] user = await mongo.users_collection.find_one({"id": user_id}, {"_id": False}) pay_rate = await mongo.pay_rates_collection.find_one( {"id": user.get("payRate", {}).get("id")}, {"_id": False} ) pauses = work_shift.get("pauses", []) total_pause_time = sum( pause["end"] - pause["start"] for pause in pauses if "start" in pause and "end" in pause ) worked_time = max(end_time - start_time - total_pause_time, 0) total_hours = round(worked_time / 3_600_000, 2) total_amount = 0.0 if pay_rate and pay_rate["payrollScheme"]["key"] == "hourly": overtime_threshold = pay_rate.get("overtimeThreshold", 0) base_hours = min(total_hours, overtime_threshold) overtime_hours = max(total_hours - overtime_threshold, 0) base_rate = pay_rate.get("baseRate", 0) overtime_rate = pay_rate.get("overtimeRate", base_rate) total_amount = round(base_hours * base_rate + overtime_hours * overtime_rate, 2) today_date = datetime.now().strftime("%Y-%m-%dT00:00:00.000") existing = await mongo.time_tracking_collection.find_one({ "userId": user_id, "date": today_date }) if existing: await mongo.time_tracking_collection.update_one( {"_id": existing["_id"]}, { "$inc": { "hours": total_hours, "amount": total_amount } } ) else: await mongo.time_tracking_collection.insert_one({ "id": await mongo.get_next_id(mongo.time_tracking_collection), "date": today_date, "userId": user_id, "hours": total_hours, "amount": total_amount }) def get_time_millis(): return int(time() * 1000)