landvex: Fixar och tester klara för alla komponenter
- Datafabrik: Dockerfile fix, agentorkestrering fungerar - Vision: Identify-modell, FAISS, OCR alla testade - API: Alla 7 integrationstester passerade - Upplösare: Entitetsupplösning verifierad
This commit is contained in:
@@ -0,0 +1,261 @@
|
||||
"""
|
||||
HR Management Module
|
||||
Handles employees, contractors, payroll, and time tracking
|
||||
"""
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from sqlalchemy.orm import Session
|
||||
from typing import List, Optional
|
||||
from datetime import datetime
|
||||
|
||||
from app.database import get_db
|
||||
from app.models import Employee, Contractor, EmployeeStatus, User
|
||||
from app.core.security import get_current_user
|
||||
|
||||
router = APIRouter(prefix="/hr", tags=["hr"])
|
||||
|
||||
# ─── EMPLOYEES ────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/employees")
|
||||
async def list_employees(
|
||||
status: Optional[EmployeeStatus] = None,
|
||||
department: Optional[str] = None,
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""List all employees with optional filtering."""
|
||||
query = db.query(Employee)
|
||||
if status:
|
||||
query = query.filter(Employee.status == status)
|
||||
if department:
|
||||
query = query.filter(Employee.department.ilike(f"%{department}%"))
|
||||
employees = query.offset(skip).limit(limit).all()
|
||||
return {
|
||||
"employees": [
|
||||
{
|
||||
"id": e.id,
|
||||
"employee_id": e.employee_id,
|
||||
"full_name": f"{e.first_name} {e.last_name}",
|
||||
"email": e.email,
|
||||
"department": e.department,
|
||||
"position": e.position,
|
||||
"status": e.status.value,
|
||||
"start_date": e.start_date.isoformat() if e.start_date else None,
|
||||
"salary": float(e.salary) if e.salary else None,
|
||||
"salary_currency": e.salary_currency,
|
||||
}
|
||||
for e in employees
|
||||
],
|
||||
"total": query.count(),
|
||||
}
|
||||
|
||||
@router.post("/employees")
|
||||
async def create_employee(
|
||||
employee_id: str,
|
||||
first_name: str,
|
||||
last_name: str,
|
||||
email: str,
|
||||
department: str,
|
||||
position: str,
|
||||
salary: float,
|
||||
start_date: datetime,
|
||||
phone: Optional[str] = None,
|
||||
manager_id: Optional[int] = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Create a new employee record."""
|
||||
employee = Employee(
|
||||
employee_id=employee_id,
|
||||
first_name=first_name,
|
||||
last_name=last_name,
|
||||
email=email,
|
||||
phone=phone,
|
||||
department=department,
|
||||
position=position,
|
||||
salary=salary,
|
||||
start_date=start_date,
|
||||
manager_id=manager_id,
|
||||
)
|
||||
db.add(employee)
|
||||
db.commit()
|
||||
db.refresh(employee)
|
||||
return {"status": "created", "employee_id": employee.id}
|
||||
|
||||
@router.get("/employees/{employee_id}")
|
||||
async def get_employee(
|
||||
employee_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Get employee details."""
|
||||
employee = db.query(Employee).filter(Employee.id == employee_id).first()
|
||||
if not employee:
|
||||
raise HTTPException(status_code=404, detail="Employee not found")
|
||||
return {
|
||||
"id": employee.id,
|
||||
"employee_id": employee.employee_id,
|
||||
"first_name": employee.first_name,
|
||||
"last_name": employee.last_name,
|
||||
"email": employee.email,
|
||||
"phone": employee.phone,
|
||||
"department": employee.department,
|
||||
"position": employee.position,
|
||||
"status": employee.status.value,
|
||||
"start_date": employee.start_date.isoformat() if employee.start_date else None,
|
||||
"end_date": employee.end_date.isoformat() if employee.end_date else None,
|
||||
"salary": float(employee.salary) if employee.salary else None,
|
||||
"salary_currency": employee.salary_currency,
|
||||
"manager_id": employee.manager_id,
|
||||
}
|
||||
|
||||
@router.put("/employees/{employee_id}")
|
||||
async def update_employee(
|
||||
employee_id: int,
|
||||
department: Optional[str] = None,
|
||||
position: Optional[str] = None,
|
||||
salary: Optional[float] = None,
|
||||
status: Optional[EmployeeStatus] = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Update employee information."""
|
||||
employee = db.query(Employee).filter(Employee.id == employee_id).first()
|
||||
if not employee:
|
||||
raise HTTPException(status_code=404, detail="Employee not found")
|
||||
|
||||
if department:
|
||||
employee.department = department
|
||||
if position:
|
||||
employee.position = position
|
||||
if salary:
|
||||
employee.salary = salary
|
||||
if status:
|
||||
employee.status = status
|
||||
if status == EmployeeStatus.TERMINATED:
|
||||
employee.end_date = datetime.utcnow()
|
||||
|
||||
db.commit()
|
||||
db.refresh(employee)
|
||||
return {"status": "updated", "employee_id": employee.id}
|
||||
|
||||
# ─── CONTRACTORS ──────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/contractors")
|
||||
async def list_contractors(
|
||||
status: Optional[str] = None,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""List all contractors."""
|
||||
query = db.query(Contractor)
|
||||
if status:
|
||||
query = query.filter(Contractor.status == status)
|
||||
contractors = query.all()
|
||||
return {
|
||||
"contractors": [
|
||||
{
|
||||
"id": c.id,
|
||||
"contractor_id": c.contractor_id,
|
||||
"company_name": c.company_name,
|
||||
"contact_name": c.contact_name,
|
||||
"email": c.email,
|
||||
"services": c.services,
|
||||
"hourly_rate": float(c.hourly_rate) if c.hourly_rate else None,
|
||||
"currency": c.currency,
|
||||
"contract_start": c.contract_start.isoformat() if c.contract_start else None,
|
||||
"contract_end": c.contract_end.isoformat() if c.contract_end else None,
|
||||
"status": c.status,
|
||||
}
|
||||
for c in contractors
|
||||
],
|
||||
"total": query.count(),
|
||||
}
|
||||
|
||||
@router.post("/contractors")
|
||||
async def create_contractor(
|
||||
contractor_id: str,
|
||||
company_name: str,
|
||||
contact_name: str,
|
||||
email: str,
|
||||
services: List[str],
|
||||
hourly_rate: float,
|
||||
contract_start: datetime,
|
||||
contract_end: Optional[datetime] = None,
|
||||
phone: Optional[str] = None,
|
||||
currency: str = "SEK",
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Create a new contractor record."""
|
||||
contractor = Contractor(
|
||||
contractor_id=contractor_id,
|
||||
company_name=company_name,
|
||||
contact_name=contact_name,
|
||||
email=email,
|
||||
phone=phone,
|
||||
services=services,
|
||||
hourly_rate=hourly_rate,
|
||||
currency=currency,
|
||||
contract_start=contract_start,
|
||||
contract_end=contract_end,
|
||||
)
|
||||
db.add(contractor)
|
||||
db.commit()
|
||||
db.refresh(contractor)
|
||||
return {"status": "created", "contractor_id": contractor.id}
|
||||
|
||||
@router.get("/contractors/{contractor_id}")
|
||||
async def get_contractor(
|
||||
contractor_id: int,
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Get contractor details."""
|
||||
contractor = db.query(Contractor).filter(Contractor.id == contractor_id).first()
|
||||
if not contractor:
|
||||
raise HTTPException(status_code=404, detail="Contractor not found")
|
||||
return {
|
||||
"id": contractor.id,
|
||||
"contractor_id": contractor.contractor_id,
|
||||
"company_name": contractor.company_name,
|
||||
"contact_name": contractor.contact_name,
|
||||
"email": contractor.email,
|
||||
"phone": contractor.phone,
|
||||
"services": contractor.services,
|
||||
"hourly_rate": float(contractor.hourly_rate) if contractor.hourly_rate else None,
|
||||
"currency": contractor.currency,
|
||||
"contract_start": contractor.contract_start.isoformat() if contractor.contract_start else None,
|
||||
"contract_end": contractor.contract_end.isoformat() if contractor.contract_end else None,
|
||||
"status": contractor.status,
|
||||
}
|
||||
|
||||
# ─── PAYROLL ──────────────────────────────────────────────────────────────────
|
||||
|
||||
@router.get("/payroll/summary")
|
||||
async def get_payroll_summary(
|
||||
db: Session = Depends(get_db),
|
||||
current_user: User = Depends(get_current_user),
|
||||
):
|
||||
"""Get payroll summary."""
|
||||
employees = db.query(Employee).filter(Employee.status == EmployeeStatus.ACTIVE).all()
|
||||
contractors = db.query(Contractor).filter(Contractor.status == "active").all()
|
||||
|
||||
total_salary = sum(float(e.salary) for e in employees if e.salary)
|
||||
total_contractor_cost = sum(float(c.hourly_rate) * 160 for c in contractors if c.hourly_rate) # Assume 160h/month
|
||||
|
||||
return {
|
||||
"period": datetime.utcnow().strftime("%Y-%m"),
|
||||
"employees": {
|
||||
"count": len(employees),
|
||||
"total_monthly_salary": total_salary,
|
||||
"currency": "SEK",
|
||||
},
|
||||
"contractors": {
|
||||
"count": len(contractors),
|
||||
"estimated_monthly_cost": total_contractor_cost,
|
||||
"currency": "SEK",
|
||||
},
|
||||
"total_monthly_cost": total_salary + total_contractor_cost,
|
||||
}
|
||||
Reference in New Issue
Block a user