320 lines
9.4 KiB
Python
320 lines
9.4 KiB
Python
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)
|