Files
CRM-OLD-API/app/api/v1/work_shifts.py
2025-07-24 20:13:47 +03:00

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)