Add HR agent onboarding model and actor enforcement
This commit is contained in:
@@ -3,10 +3,10 @@ from __future__ import annotations
|
|||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
from app.api.utils import log_activity
|
from app.api.utils import log_activity, get_actor_employee_id
|
||||||
from app.db.session import get_session
|
from app.db.session import get_session
|
||||||
from app.models.hr import EmploymentAction, HeadcountRequest
|
from app.models.hr import EmploymentAction, HeadcountRequest, AgentOnboarding
|
||||||
from app.schemas.hr import EmploymentActionCreate, HeadcountRequestCreate, HeadcountRequestUpdate
|
from app.schemas.hr import EmploymentActionCreate, HeadcountRequestCreate, HeadcountRequestUpdate, AgentOnboardingCreate, AgentOnboardingUpdate
|
||||||
|
|
||||||
router = APIRouter(prefix="/hr", tags=["hr"])
|
router = APIRouter(prefix="/hr", tags=["hr"])
|
||||||
|
|
||||||
@@ -17,18 +17,18 @@ def list_headcount_requests(session: Session = Depends(get_session)):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/headcount", response_model=HeadcountRequest)
|
@router.post("/headcount", response_model=HeadcountRequest)
|
||||||
def create_headcount_request(payload: HeadcountRequestCreate, session: Session = Depends(get_session)):
|
def create_headcount_request(payload: HeadcountRequestCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
req = HeadcountRequest(**payload.model_dump())
|
req = HeadcountRequest(**payload.model_dump())
|
||||||
session.add(req)
|
session.add(req)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(req)
|
session.refresh(req)
|
||||||
log_activity(session, actor_employee_id=req.requested_by_manager_id, entity_type="headcount_request", entity_id=req.id, verb="submitted")
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="headcount_request", entity_id=req.id, verb="submitted")
|
||||||
session.commit()
|
session.commit()
|
||||||
return req
|
return req
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/headcount/{request_id}", response_model=HeadcountRequest)
|
@router.patch("/headcount/{request_id}", response_model=HeadcountRequest)
|
||||||
def update_headcount_request(request_id: int, payload: HeadcountRequestUpdate, session: Session = Depends(get_session)):
|
def update_headcount_request(request_id: int, payload: HeadcountRequestUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
req = session.get(HeadcountRequest, request_id)
|
req = session.get(HeadcountRequest, request_id)
|
||||||
if not req:
|
if not req:
|
||||||
raise HTTPException(status_code=404, detail="Request not found")
|
raise HTTPException(status_code=404, detail="Request not found")
|
||||||
@@ -40,7 +40,7 @@ def update_headcount_request(request_id: int, payload: HeadcountRequestUpdate, s
|
|||||||
session.add(req)
|
session.add(req)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(req)
|
session.refresh(req)
|
||||||
log_activity(session, actor_employee_id=req.requested_by_manager_id, entity_type="headcount_request", entity_id=req.id, verb="updated", payload=data)
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="headcount_request", entity_id=req.id, verb="updated", payload=data)
|
||||||
session.commit()
|
session.commit()
|
||||||
return req
|
return req
|
||||||
|
|
||||||
@@ -51,11 +51,47 @@ def list_employment_actions(session: Session = Depends(get_session)):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/actions", response_model=EmploymentAction)
|
@router.post("/actions", response_model=EmploymentAction)
|
||||||
def create_employment_action(payload: EmploymentActionCreate, session: Session = Depends(get_session)):
|
def create_employment_action(payload: EmploymentActionCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
action = EmploymentAction(**payload.model_dump())
|
action = EmploymentAction(**payload.model_dump())
|
||||||
session.add(action)
|
session.add(action)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(action)
|
session.refresh(action)
|
||||||
log_activity(session, actor_employee_id=action.issued_by_employee_id, entity_type="employment_action", entity_id=action.id, verb=action.action_type, payload={"employee_id": action.employee_id})
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="employment_action", entity_id=action.id, verb=action.action_type, payload={"employee_id": action.employee_id})
|
||||||
session.commit()
|
session.commit()
|
||||||
return action
|
return action
|
||||||
|
|
||||||
|
@router.get("/onboarding", response_model=list[AgentOnboarding])
|
||||||
|
def list_agent_onboarding(session: Session = Depends(get_session)):
|
||||||
|
return session.exec(select(AgentOnboarding).order_by(AgentOnboarding.id.desc())).all()
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/onboarding", response_model=AgentOnboarding)
|
||||||
|
def create_agent_onboarding(payload: AgentOnboardingCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
|
item = AgentOnboarding(**payload.model_dump())
|
||||||
|
session.add(item)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(item)
|
||||||
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="agent_onboarding", entity_id=item.id, verb="created", payload={"agent_name": item.agent_name, "status": item.status})
|
||||||
|
session.commit()
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
@router.patch("/onboarding/{onboarding_id}", response_model=AgentOnboarding)
|
||||||
|
def update_agent_onboarding(onboarding_id: int, payload: AgentOnboardingUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
|
item = session.get(AgentOnboarding, onboarding_id)
|
||||||
|
if not item:
|
||||||
|
raise HTTPException(status_code=404, detail="Onboarding record not found")
|
||||||
|
|
||||||
|
data = payload.model_dump(exclude_unset=True)
|
||||||
|
for k, v in data.items():
|
||||||
|
setattr(item, k, v)
|
||||||
|
from datetime import datetime
|
||||||
|
item.updated_at = datetime.utcnow()
|
||||||
|
|
||||||
|
session.add(item)
|
||||||
|
session.commit()
|
||||||
|
session.refresh(item)
|
||||||
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="agent_onboarding", entity_id=item.id, verb="updated", payload=data)
|
||||||
|
session.commit()
|
||||||
|
return item
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
from app.api.utils import log_activity
|
from app.api.utils import log_activity, get_actor_employee_id
|
||||||
from app.db.session import get_session
|
from app.db.session import get_session
|
||||||
from app.models.org import Department, Employee
|
from app.models.org import Department, Employee
|
||||||
from app.schemas.org import DepartmentCreate, DepartmentUpdate, EmployeeCreate, EmployeeUpdate
|
from app.schemas.org import DepartmentCreate, DepartmentUpdate, EmployeeCreate, EmployeeUpdate
|
||||||
@@ -17,18 +17,18 @@ def list_departments(session: Session = Depends(get_session)):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/departments", response_model=Department)
|
@router.post("/departments", response_model=Department)
|
||||||
def create_department(payload: DepartmentCreate, session: Session = Depends(get_session)):
|
def create_department(payload: DepartmentCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
dept = Department(name=payload.name, head_employee_id=payload.head_employee_id)
|
dept = Department(name=payload.name, head_employee_id=payload.head_employee_id)
|
||||||
session.add(dept)
|
session.add(dept)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(dept)
|
session.refresh(dept)
|
||||||
log_activity(session, actor_employee_id=None, entity_type="department", entity_id=dept.id, verb="created", payload={"name": dept.name})
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="department", entity_id=dept.id, verb="created", payload={"name": dept.name})
|
||||||
session.commit()
|
session.commit()
|
||||||
return dept
|
return dept
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/departments/{department_id}", response_model=Department)
|
@router.patch("/departments/{department_id}", response_model=Department)
|
||||||
def update_department(department_id: int, payload: DepartmentUpdate, session: Session = Depends(get_session)):
|
def update_department(department_id: int, payload: DepartmentUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
dept = session.get(Department, department_id)
|
dept = session.get(Department, department_id)
|
||||||
if not dept:
|
if not dept:
|
||||||
raise HTTPException(status_code=404, detail="Department not found")
|
raise HTTPException(status_code=404, detail="Department not found")
|
||||||
@@ -40,7 +40,7 @@ def update_department(department_id: int, payload: DepartmentUpdate, session: Se
|
|||||||
session.add(dept)
|
session.add(dept)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(dept)
|
session.refresh(dept)
|
||||||
log_activity(session, actor_employee_id=None, entity_type="department", entity_id=dept.id, verb="updated", payload=data)
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="department", entity_id=dept.id, verb="updated", payload=data)
|
||||||
session.commit()
|
session.commit()
|
||||||
return dept
|
return dept
|
||||||
|
|
||||||
@@ -51,18 +51,18 @@ def list_employees(session: Session = Depends(get_session)):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/employees", response_model=Employee)
|
@router.post("/employees", response_model=Employee)
|
||||||
def create_employee(payload: EmployeeCreate, session: Session = Depends(get_session)):
|
def create_employee(payload: EmployeeCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
emp = Employee(**payload.model_dump())
|
emp = Employee(**payload.model_dump())
|
||||||
session.add(emp)
|
session.add(emp)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(emp)
|
session.refresh(emp)
|
||||||
log_activity(session, actor_employee_id=None, entity_type="employee", entity_id=emp.id, verb="created", payload={"name": emp.name, "type": emp.employee_type})
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="employee", entity_id=emp.id, verb="created", payload={"name": emp.name, "type": emp.employee_type})
|
||||||
session.commit()
|
session.commit()
|
||||||
return emp
|
return emp
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/employees/{employee_id}", response_model=Employee)
|
@router.patch("/employees/{employee_id}", response_model=Employee)
|
||||||
def update_employee(employee_id: int, payload: EmployeeUpdate, session: Session = Depends(get_session)):
|
def update_employee(employee_id: int, payload: EmployeeUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
emp = session.get(Employee, employee_id)
|
emp = session.get(Employee, employee_id)
|
||||||
if not emp:
|
if not emp:
|
||||||
raise HTTPException(status_code=404, detail="Employee not found")
|
raise HTTPException(status_code=404, detail="Employee not found")
|
||||||
@@ -74,6 +74,6 @@ def update_employee(employee_id: int, payload: EmployeeUpdate, session: Session
|
|||||||
session.add(emp)
|
session.add(emp)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(emp)
|
session.refresh(emp)
|
||||||
log_activity(session, actor_employee_id=None, entity_type="employee", entity_id=emp.id, verb="updated", payload=data)
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="employee", entity_id=emp.id, verb="updated", payload=data)
|
||||||
session.commit()
|
session.commit()
|
||||||
return emp
|
return emp
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
|||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
from app.api.utils import log_activity
|
from app.api.utils import log_activity, get_actor_employee_id
|
||||||
from app.db.session import get_session
|
from app.db.session import get_session
|
||||||
from app.models.projects import Project, ProjectMember
|
from app.models.projects import Project, ProjectMember
|
||||||
from app.schemas.projects import ProjectCreate, ProjectUpdate
|
from app.schemas.projects import ProjectCreate, ProjectUpdate
|
||||||
@@ -17,18 +17,18 @@ def list_projects(session: Session = Depends(get_session)):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("", response_model=Project)
|
@router.post("", response_model=Project)
|
||||||
def create_project(payload: ProjectCreate, session: Session = Depends(get_session)):
|
def create_project(payload: ProjectCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
proj = Project(**payload.model_dump())
|
proj = Project(**payload.model_dump())
|
||||||
session.add(proj)
|
session.add(proj)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(proj)
|
session.refresh(proj)
|
||||||
log_activity(session, actor_employee_id=None, entity_type="project", entity_id=proj.id, verb="created", payload={"name": proj.name})
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="project", entity_id=proj.id, verb="created", payload={"name": proj.name})
|
||||||
session.commit()
|
session.commit()
|
||||||
return proj
|
return proj
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/{project_id}", response_model=Project)
|
@router.patch("/{project_id}", response_model=Project)
|
||||||
def update_project(project_id: int, payload: ProjectUpdate, session: Session = Depends(get_session)):
|
def update_project(project_id: int, payload: ProjectUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
proj = session.get(Project, project_id)
|
proj = session.get(Project, project_id)
|
||||||
if not proj:
|
if not proj:
|
||||||
raise HTTPException(status_code=404, detail="Project not found")
|
raise HTTPException(status_code=404, detail="Project not found")
|
||||||
@@ -40,7 +40,7 @@ def update_project(project_id: int, payload: ProjectUpdate, session: Session = D
|
|||||||
session.add(proj)
|
session.add(proj)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(proj)
|
session.refresh(proj)
|
||||||
log_activity(session, actor_employee_id=None, entity_type="project", entity_id=proj.id, verb="updated", payload=data)
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="project", entity_id=proj.id, verb="updated", payload=data)
|
||||||
session.commit()
|
session.commit()
|
||||||
return proj
|
return proj
|
||||||
|
|
||||||
@@ -53,7 +53,7 @@ def list_project_members(project_id: int, session: Session = Depends(get_session
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/{project_id}/members", response_model=ProjectMember)
|
@router.post("/{project_id}/members", response_model=ProjectMember)
|
||||||
def add_project_member(project_id: int, payload: ProjectMember, session: Session = Depends(get_session)):
|
def add_project_member(project_id: int, payload: ProjectMember, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
existing = session.exec(select(ProjectMember).where(ProjectMember.project_id == project_id, ProjectMember.employee_id == payload.employee_id)).first()
|
existing = session.exec(select(ProjectMember).where(ProjectMember.project_id == project_id, ProjectMember.employee_id == payload.employee_id)).first()
|
||||||
if existing:
|
if existing:
|
||||||
raise HTTPException(status_code=409, detail="Member already added")
|
raise HTTPException(status_code=409, detail="Member already added")
|
||||||
@@ -63,7 +63,7 @@ def add_project_member(project_id: int, payload: ProjectMember, session: Session
|
|||||||
session.refresh(member)
|
session.refresh(member)
|
||||||
log_activity(
|
log_activity(
|
||||||
session,
|
session,
|
||||||
actor_employee_id=None,
|
actor_employee_id=actor_employee_id,
|
||||||
entity_type="project_member",
|
entity_type="project_member",
|
||||||
entity_id=member.id,
|
entity_id=member.id,
|
||||||
verb="added",
|
verb="added",
|
||||||
@@ -74,7 +74,7 @@ def add_project_member(project_id: int, payload: ProjectMember, session: Session
|
|||||||
|
|
||||||
|
|
||||||
@router.delete("/{project_id}/members/{member_id}")
|
@router.delete("/{project_id}/members/{member_id}")
|
||||||
def remove_project_member(project_id: int, member_id: int, session: Session = Depends(get_session)):
|
def remove_project_member(project_id: int, member_id: int, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
member = session.get(ProjectMember, member_id)
|
member = session.get(ProjectMember, member_id)
|
||||||
if not member or member.project_id != project_id:
|
if not member or member.project_id != project_id:
|
||||||
raise HTTPException(status_code=404, detail="Project member not found")
|
raise HTTPException(status_code=404, detail="Project member not found")
|
||||||
@@ -82,7 +82,7 @@ def remove_project_member(project_id: int, member_id: int, session: Session = De
|
|||||||
session.commit()
|
session.commit()
|
||||||
log_activity(
|
log_activity(
|
||||||
session,
|
session,
|
||||||
actor_employee_id=None,
|
actor_employee_id=actor_employee_id,
|
||||||
entity_type="project_member",
|
entity_type="project_member",
|
||||||
entity_id=member_id,
|
entity_id=member_id,
|
||||||
verb="removed",
|
verb="removed",
|
||||||
@@ -93,7 +93,7 @@ def remove_project_member(project_id: int, member_id: int, session: Session = De
|
|||||||
|
|
||||||
|
|
||||||
@router.patch("/{project_id}/members/{member_id}", response_model=ProjectMember)
|
@router.patch("/{project_id}/members/{member_id}", response_model=ProjectMember)
|
||||||
def update_project_member(project_id: int, member_id: int, payload: ProjectMember, session: Session = Depends(get_session)):
|
def update_project_member(project_id: int, member_id: int, payload: ProjectMember, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
member = session.get(ProjectMember, member_id)
|
member = session.get(ProjectMember, member_id)
|
||||||
if not member or member.project_id != project_id:
|
if not member or member.project_id != project_id:
|
||||||
raise HTTPException(status_code=404, detail="Project member not found")
|
raise HTTPException(status_code=404, detail="Project member not found")
|
||||||
@@ -106,7 +106,7 @@ def update_project_member(project_id: int, member_id: int, payload: ProjectMembe
|
|||||||
session.refresh(member)
|
session.refresh(member)
|
||||||
log_activity(
|
log_activity(
|
||||||
session,
|
session,
|
||||||
actor_employee_id=None,
|
actor_employee_id=actor_employee_id,
|
||||||
entity_type="project_member",
|
entity_type="project_member",
|
||||||
entity_id=member.id,
|
entity_id=member.id,
|
||||||
verb="updated",
|
verb="updated",
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
import json
|
import json
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from fastapi import Header, HTTPException
|
||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
from app.models.activity import Activity
|
from app.models.activity import Activity
|
||||||
@@ -26,3 +27,11 @@ def log_activity(
|
|||||||
payload_json=json.dumps(payload) if payload is not None else None,
|
payload_json=json.dumps(payload) if payload is not None else None,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_actor_employee_id(
|
||||||
|
x_actor_employee_id: int | None = Header(default=None, alias="X-Actor-Employee-Id"),
|
||||||
|
) -> int:
|
||||||
|
if x_actor_employee_id is None:
|
||||||
|
raise HTTPException(status_code=400, detail="X-Actor-Employee-Id required")
|
||||||
|
return x_actor_employee_id
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ from datetime import datetime
|
|||||||
from fastapi import APIRouter, Depends, HTTPException
|
from fastapi import APIRouter, Depends, HTTPException
|
||||||
from sqlmodel import Session, select
|
from sqlmodel import Session, select
|
||||||
|
|
||||||
from app.api.utils import log_activity
|
from app.api.utils import log_activity, get_actor_employee_id
|
||||||
from app.db.session import get_session
|
from app.db.session import get_session
|
||||||
from app.models.work import Task, TaskComment
|
from app.models.work import Task, TaskComment
|
||||||
from app.schemas.work import TaskCommentCreate, TaskCreate, TaskUpdate
|
from app.schemas.work import TaskCommentCreate, TaskCreate, TaskUpdate
|
||||||
@@ -24,7 +24,9 @@ def list_tasks(project_id: int | None = None, session: Session = Depends(get_ses
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/tasks", response_model=Task)
|
@router.post("/tasks", response_model=Task)
|
||||||
def create_task(payload: TaskCreate, session: Session = Depends(get_session)):
|
def create_task(payload: TaskCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
|
if payload.created_by_employee_id is None:
|
||||||
|
payload = TaskCreate(**{**payload.model_dump(), "created_by_employee_id": actor_employee_id})
|
||||||
task = Task(**payload.model_dump())
|
task = Task(**payload.model_dump())
|
||||||
if task.status not in ALLOWED_STATUSES:
|
if task.status not in ALLOWED_STATUSES:
|
||||||
raise HTTPException(status_code=400, detail="Invalid status")
|
raise HTTPException(status_code=400, detail="Invalid status")
|
||||||
@@ -34,7 +36,7 @@ def create_task(payload: TaskCreate, session: Session = Depends(get_session)):
|
|||||||
session.refresh(task)
|
session.refresh(task)
|
||||||
log_activity(
|
log_activity(
|
||||||
session,
|
session,
|
||||||
actor_employee_id=task.created_by_employee_id,
|
actor_employee_id=actor_employee_id,
|
||||||
entity_type="task",
|
entity_type="task",
|
||||||
entity_id=task.id,
|
entity_id=task.id,
|
||||||
verb="created",
|
verb="created",
|
||||||
@@ -45,7 +47,7 @@ def create_task(payload: TaskCreate, session: Session = Depends(get_session)):
|
|||||||
|
|
||||||
|
|
||||||
@router.patch("/tasks/{task_id}", response_model=Task)
|
@router.patch("/tasks/{task_id}", response_model=Task)
|
||||||
def update_task(task_id: int, payload: TaskUpdate, session: Session = Depends(get_session)):
|
def update_task(task_id: int, payload: TaskUpdate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
task = session.get(Task, task_id)
|
task = session.get(Task, task_id)
|
||||||
if not task:
|
if not task:
|
||||||
raise HTTPException(status_code=404, detail="Task not found")
|
raise HTTPException(status_code=404, detail="Task not found")
|
||||||
@@ -60,19 +62,19 @@ def update_task(task_id: int, payload: TaskUpdate, session: Session = Depends(ge
|
|||||||
session.add(task)
|
session.add(task)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(task)
|
session.refresh(task)
|
||||||
log_activity(session, actor_employee_id=None, entity_type="task", entity_id=task.id, verb="updated", payload=data)
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=task.id, verb="updated", payload=data)
|
||||||
session.commit()
|
session.commit()
|
||||||
return task
|
return task
|
||||||
|
|
||||||
|
|
||||||
@router.delete("/tasks/{task_id}")
|
@router.delete("/tasks/{task_id}")
|
||||||
def delete_task(task_id: int, session: Session = Depends(get_session)):
|
def delete_task(task_id: int, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
task = session.get(Task, task_id)
|
task = session.get(Task, task_id)
|
||||||
if not task:
|
if not task:
|
||||||
raise HTTPException(status_code=404, detail="Task not found")
|
raise HTTPException(status_code=404, detail="Task not found")
|
||||||
session.delete(task)
|
session.delete(task)
|
||||||
session.commit()
|
session.commit()
|
||||||
log_activity(session, actor_employee_id=None, entity_type="task", entity_id=task_id, verb="deleted")
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=task_id, verb="deleted")
|
||||||
session.commit()
|
session.commit()
|
||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
@@ -83,11 +85,13 @@ def list_task_comments(task_id: int, session: Session = Depends(get_session)):
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/task-comments", response_model=TaskComment)
|
@router.post("/task-comments", response_model=TaskComment)
|
||||||
def create_task_comment(payload: TaskCommentCreate, session: Session = Depends(get_session)):
|
def create_task_comment(payload: TaskCommentCreate, session: Session = Depends(get_session), actor_employee_id: int = Depends(get_actor_employee_id)):
|
||||||
|
if payload.author_employee_id is None:
|
||||||
|
payload = TaskCommentCreate(**{**payload.model_dump(), "author_employee_id": actor_employee_id})
|
||||||
c = TaskComment(**payload.model_dump())
|
c = TaskComment(**payload.model_dump())
|
||||||
session.add(c)
|
session.add(c)
|
||||||
session.commit()
|
session.commit()
|
||||||
session.refresh(c)
|
session.refresh(c)
|
||||||
log_activity(session, actor_employee_id=c.author_employee_id, entity_type="task", entity_id=c.task_id, verb="commented")
|
log_activity(session, actor_employee_id=actor_employee_id, entity_type="task", entity_id=c.task_id, verb="commented")
|
||||||
session.commit()
|
session.commit()
|
||||||
return c
|
return c
|
||||||
|
|||||||
@@ -33,3 +33,23 @@ class EmploymentAction(SQLModel, table=True):
|
|||||||
notes: str | None = None
|
notes: str | None = None
|
||||||
|
|
||||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|
||||||
|
|
||||||
|
class AgentOnboarding(SQLModel, table=True):
|
||||||
|
__tablename__ = "agent_onboardings"
|
||||||
|
|
||||||
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
|
agent_name: str
|
||||||
|
role_title: str
|
||||||
|
prompt: str
|
||||||
|
cron_interval_ms: int | None = None
|
||||||
|
tools_json: str | None = None
|
||||||
|
owner_hr_id: int | None = Field(default=None, foreign_key="employees.id")
|
||||||
|
|
||||||
|
status: str = Field(default="planned") # planned|spawning|spawned|verified|blocked
|
||||||
|
spawned_agent_id: str | None = None
|
||||||
|
session_key: str | None = None
|
||||||
|
|
||||||
|
notes: str | None = None
|
||||||
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
||||||
|
|||||||
@@ -22,3 +22,30 @@ class EmploymentActionCreate(SQLModel):
|
|||||||
issued_by_employee_id: int
|
issued_by_employee_id: int
|
||||||
action_type: str
|
action_type: str
|
||||||
notes: str | None = None
|
notes: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class AgentOnboardingCreate(SQLModel):
|
||||||
|
agent_name: str
|
||||||
|
role_title: str
|
||||||
|
prompt: str
|
||||||
|
cron_interval_ms: int | None = None
|
||||||
|
tools_json: str | None = None
|
||||||
|
owner_hr_id: int | None = None
|
||||||
|
status: str = "planned"
|
||||||
|
spawned_agent_id: str | None = None
|
||||||
|
session_key: str | None = None
|
||||||
|
notes: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class AgentOnboardingUpdate(SQLModel):
|
||||||
|
agent_name: str | None = None
|
||||||
|
role_title: str | None = None
|
||||||
|
prompt: str | None = None
|
||||||
|
cron_interval_ms: int | None = None
|
||||||
|
tools_json: str | None = None
|
||||||
|
owner_hr_id: int | None = None
|
||||||
|
status: str | None = None
|
||||||
|
spawned_agent_id: str | None = None
|
||||||
|
session_key: str | None = None
|
||||||
|
notes: str | None = None
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ import type {
|
|||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
|
AgentOnboarding,
|
||||||
|
AgentOnboardingCreate,
|
||||||
|
AgentOnboardingUpdate,
|
||||||
EmploymentAction,
|
EmploymentAction,
|
||||||
EmploymentActionCreate,
|
EmploymentActionCreate,
|
||||||
HTTPValidationError,
|
HTTPValidationError,
|
||||||
@@ -748,3 +751,434 @@ export const useCreateEmploymentActionHrActionsPost = <
|
|||||||
queryClient,
|
queryClient,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* @summary List Agent Onboarding
|
||||||
|
*/
|
||||||
|
export type listAgentOnboardingHrOnboardingGetResponse200 = {
|
||||||
|
data: AgentOnboarding[];
|
||||||
|
status: 200;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type listAgentOnboardingHrOnboardingGetResponseSuccess =
|
||||||
|
listAgentOnboardingHrOnboardingGetResponse200 & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
export type listAgentOnboardingHrOnboardingGetResponse =
|
||||||
|
listAgentOnboardingHrOnboardingGetResponseSuccess;
|
||||||
|
|
||||||
|
export const getListAgentOnboardingHrOnboardingGetUrl = () => {
|
||||||
|
return `/hr/onboarding`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const listAgentOnboardingHrOnboardingGet = async (
|
||||||
|
options?: RequestInit,
|
||||||
|
): Promise<listAgentOnboardingHrOnboardingGetResponse> => {
|
||||||
|
return customFetch<listAgentOnboardingHrOnboardingGetResponse>(
|
||||||
|
getListAgentOnboardingHrOnboardingGetUrl(),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
method: "GET",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getListAgentOnboardingHrOnboardingGetQueryKey = () => {
|
||||||
|
return [`/hr/onboarding`] as const;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getListAgentOnboardingHrOnboardingGetQueryOptions = <
|
||||||
|
TData = Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(options?: {
|
||||||
|
query?: Partial<
|
||||||
|
UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
}) => {
|
||||||
|
const { query: queryOptions, request: requestOptions } = options ?? {};
|
||||||
|
|
||||||
|
const queryKey =
|
||||||
|
queryOptions?.queryKey ?? getListAgentOnboardingHrOnboardingGetQueryKey();
|
||||||
|
|
||||||
|
const queryFn: QueryFunction<
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>
|
||||||
|
> = ({ signal }) =>
|
||||||
|
listAgentOnboardingHrOnboardingGet({ signal, ...requestOptions });
|
||||||
|
|
||||||
|
return { queryKey, queryFn, ...queryOptions } as UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ListAgentOnboardingHrOnboardingGetQueryResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>
|
||||||
|
>;
|
||||||
|
export type ListAgentOnboardingHrOnboardingGetQueryError = unknown;
|
||||||
|
|
||||||
|
export function useListAgentOnboardingHrOnboardingGet<
|
||||||
|
TData = Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(
|
||||||
|
options: {
|
||||||
|
query: Partial<
|
||||||
|
UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>
|
||||||
|
> &
|
||||||
|
Pick<
|
||||||
|
DefinedInitialDataOptions<
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError,
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>
|
||||||
|
>,
|
||||||
|
"initialData"
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): DefinedUseQueryResult<TData, TError> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
};
|
||||||
|
export function useListAgentOnboardingHrOnboardingGet<
|
||||||
|
TData = Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(
|
||||||
|
options?: {
|
||||||
|
query?: Partial<
|
||||||
|
UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>
|
||||||
|
> &
|
||||||
|
Pick<
|
||||||
|
UndefinedInitialDataOptions<
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError,
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>
|
||||||
|
>,
|
||||||
|
"initialData"
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
};
|
||||||
|
export function useListAgentOnboardingHrOnboardingGet<
|
||||||
|
TData = Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(
|
||||||
|
options?: {
|
||||||
|
query?: Partial<
|
||||||
|
UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @summary List Agent Onboarding
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function useListAgentOnboardingHrOnboardingGet<
|
||||||
|
TData = Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError = unknown,
|
||||||
|
>(
|
||||||
|
options?: {
|
||||||
|
query?: Partial<
|
||||||
|
UseQueryOptions<
|
||||||
|
Awaited<ReturnType<typeof listAgentOnboardingHrOnboardingGet>>,
|
||||||
|
TError,
|
||||||
|
TData
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): UseQueryResult<TData, TError> & {
|
||||||
|
queryKey: DataTag<QueryKey, TData, TError>;
|
||||||
|
} {
|
||||||
|
const queryOptions =
|
||||||
|
getListAgentOnboardingHrOnboardingGetQueryOptions(options);
|
||||||
|
|
||||||
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<
|
||||||
|
TData,
|
||||||
|
TError
|
||||||
|
> & { queryKey: DataTag<QueryKey, TData, TError> };
|
||||||
|
|
||||||
|
return { ...query, queryKey: queryOptions.queryKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Create Agent Onboarding
|
||||||
|
*/
|
||||||
|
export type createAgentOnboardingHrOnboardingPostResponse200 = {
|
||||||
|
data: AgentOnboarding;
|
||||||
|
status: 200;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type createAgentOnboardingHrOnboardingPostResponse422 = {
|
||||||
|
data: HTTPValidationError;
|
||||||
|
status: 422;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type createAgentOnboardingHrOnboardingPostResponseSuccess =
|
||||||
|
createAgentOnboardingHrOnboardingPostResponse200 & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
export type createAgentOnboardingHrOnboardingPostResponseError =
|
||||||
|
createAgentOnboardingHrOnboardingPostResponse422 & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type createAgentOnboardingHrOnboardingPostResponse =
|
||||||
|
| createAgentOnboardingHrOnboardingPostResponseSuccess
|
||||||
|
| createAgentOnboardingHrOnboardingPostResponseError;
|
||||||
|
|
||||||
|
export const getCreateAgentOnboardingHrOnboardingPostUrl = () => {
|
||||||
|
return `/hr/onboarding`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createAgentOnboardingHrOnboardingPost = async (
|
||||||
|
agentOnboardingCreate: AgentOnboardingCreate,
|
||||||
|
options?: RequestInit,
|
||||||
|
): Promise<createAgentOnboardingHrOnboardingPostResponse> => {
|
||||||
|
return customFetch<createAgentOnboardingHrOnboardingPostResponse>(
|
||||||
|
getCreateAgentOnboardingHrOnboardingPostUrl(),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json", ...options?.headers },
|
||||||
|
body: JSON.stringify(agentOnboardingCreate),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getCreateAgentOnboardingHrOnboardingPostMutationOptions = <
|
||||||
|
TError = HTTPValidationError,
|
||||||
|
TContext = unknown,
|
||||||
|
>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof createAgentOnboardingHrOnboardingPost>>,
|
||||||
|
TError,
|
||||||
|
{ data: AgentOnboardingCreate },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
}): UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof createAgentOnboardingHrOnboardingPost>>,
|
||||||
|
TError,
|
||||||
|
{ data: AgentOnboardingCreate },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationKey = ["createAgentOnboardingHrOnboardingPost"];
|
||||||
|
const { mutation: mutationOptions, request: requestOptions } = options
|
||||||
|
? options.mutation &&
|
||||||
|
"mutationKey" in options.mutation &&
|
||||||
|
options.mutation.mutationKey
|
||||||
|
? options
|
||||||
|
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||||
|
: { mutation: { mutationKey }, request: undefined };
|
||||||
|
|
||||||
|
const mutationFn: MutationFunction<
|
||||||
|
Awaited<ReturnType<typeof createAgentOnboardingHrOnboardingPost>>,
|
||||||
|
{ data: AgentOnboardingCreate }
|
||||||
|
> = (props) => {
|
||||||
|
const { data } = props ?? {};
|
||||||
|
|
||||||
|
return createAgentOnboardingHrOnboardingPost(data, requestOptions);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreateAgentOnboardingHrOnboardingPostMutationResult = NonNullable<
|
||||||
|
Awaited<ReturnType<typeof createAgentOnboardingHrOnboardingPost>>
|
||||||
|
>;
|
||||||
|
export type CreateAgentOnboardingHrOnboardingPostMutationBody =
|
||||||
|
AgentOnboardingCreate;
|
||||||
|
export type CreateAgentOnboardingHrOnboardingPostMutationError =
|
||||||
|
HTTPValidationError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Create Agent Onboarding
|
||||||
|
*/
|
||||||
|
export const useCreateAgentOnboardingHrOnboardingPost = <
|
||||||
|
TError = HTTPValidationError,
|
||||||
|
TContext = unknown,
|
||||||
|
>(
|
||||||
|
options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<ReturnType<typeof createAgentOnboardingHrOnboardingPost>>,
|
||||||
|
TError,
|
||||||
|
{ data: AgentOnboardingCreate },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): UseMutationResult<
|
||||||
|
Awaited<ReturnType<typeof createAgentOnboardingHrOnboardingPost>>,
|
||||||
|
TError,
|
||||||
|
{ data: AgentOnboardingCreate },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
return useMutation(
|
||||||
|
getCreateAgentOnboardingHrOnboardingPostMutationOptions(options),
|
||||||
|
queryClient,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* @summary Update Agent Onboarding
|
||||||
|
*/
|
||||||
|
export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse200 = {
|
||||||
|
data: AgentOnboarding;
|
||||||
|
status: 200;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse422 = {
|
||||||
|
data: HTTPValidationError;
|
||||||
|
status: 422;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseSuccess =
|
||||||
|
updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse200 & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseError =
|
||||||
|
updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse422 & {
|
||||||
|
headers: Headers;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse =
|
||||||
|
| updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseSuccess
|
||||||
|
| updateAgentOnboardingHrOnboardingOnboardingIdPatchResponseError;
|
||||||
|
|
||||||
|
export const getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchUrl = (
|
||||||
|
onboardingId: number,
|
||||||
|
) => {
|
||||||
|
return `/hr/onboarding/${onboardingId}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateAgentOnboardingHrOnboardingOnboardingIdPatch = async (
|
||||||
|
onboardingId: number,
|
||||||
|
agentOnboardingUpdate: AgentOnboardingUpdate,
|
||||||
|
options?: RequestInit,
|
||||||
|
): Promise<updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse> => {
|
||||||
|
return customFetch<updateAgentOnboardingHrOnboardingOnboardingIdPatchResponse>(
|
||||||
|
getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchUrl(onboardingId),
|
||||||
|
{
|
||||||
|
...options,
|
||||||
|
method: "PATCH",
|
||||||
|
headers: { "Content-Type": "application/json", ...options?.headers },
|
||||||
|
body: JSON.stringify(agentOnboardingUpdate),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationOptions =
|
||||||
|
<TError = HTTPValidationError, TContext = unknown>(options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof updateAgentOnboardingHrOnboardingOnboardingIdPatch>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
{ onboardingId: number; data: AgentOnboardingUpdate },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
}): UseMutationOptions<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof updateAgentOnboardingHrOnboardingOnboardingIdPatch>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
{ onboardingId: number; data: AgentOnboardingUpdate },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
const mutationKey = ["updateAgentOnboardingHrOnboardingOnboardingIdPatch"];
|
||||||
|
const { mutation: mutationOptions, request: requestOptions } = options
|
||||||
|
? options.mutation &&
|
||||||
|
"mutationKey" in options.mutation &&
|
||||||
|
options.mutation.mutationKey
|
||||||
|
? options
|
||||||
|
: { ...options, mutation: { ...options.mutation, mutationKey } }
|
||||||
|
: { mutation: { mutationKey }, request: undefined };
|
||||||
|
|
||||||
|
const mutationFn: MutationFunction<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof updateAgentOnboardingHrOnboardingOnboardingIdPatch>
|
||||||
|
>,
|
||||||
|
{ onboardingId: number; data: AgentOnboardingUpdate }
|
||||||
|
> = (props) => {
|
||||||
|
const { onboardingId, data } = props ?? {};
|
||||||
|
|
||||||
|
return updateAgentOnboardingHrOnboardingOnboardingIdPatch(
|
||||||
|
onboardingId,
|
||||||
|
data,
|
||||||
|
requestOptions,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return { mutationFn, ...mutationOptions };
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationResult =
|
||||||
|
NonNullable<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof updateAgentOnboardingHrOnboardingOnboardingIdPatch>
|
||||||
|
>
|
||||||
|
>;
|
||||||
|
export type UpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationBody =
|
||||||
|
AgentOnboardingUpdate;
|
||||||
|
export type UpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationError =
|
||||||
|
HTTPValidationError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @summary Update Agent Onboarding
|
||||||
|
*/
|
||||||
|
export const useUpdateAgentOnboardingHrOnboardingOnboardingIdPatch = <
|
||||||
|
TError = HTTPValidationError,
|
||||||
|
TContext = unknown,
|
||||||
|
>(
|
||||||
|
options?: {
|
||||||
|
mutation?: UseMutationOptions<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof updateAgentOnboardingHrOnboardingOnboardingIdPatch>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
{ onboardingId: number; data: AgentOnboardingUpdate },
|
||||||
|
TContext
|
||||||
|
>;
|
||||||
|
request?: SecondParameter<typeof customFetch>;
|
||||||
|
},
|
||||||
|
queryClient?: QueryClient,
|
||||||
|
): UseMutationResult<
|
||||||
|
Awaited<
|
||||||
|
ReturnType<typeof updateAgentOnboardingHrOnboardingOnboardingIdPatch>
|
||||||
|
>,
|
||||||
|
TError,
|
||||||
|
{ onboardingId: number; data: AgentOnboardingUpdate },
|
||||||
|
TContext
|
||||||
|
> => {
|
||||||
|
return useMutation(
|
||||||
|
getUpdateAgentOnboardingHrOnboardingOnboardingIdPatchMutationOptions(
|
||||||
|
options,
|
||||||
|
),
|
||||||
|
queryClient,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
22
frontend/src/api/generated/model/agentOnboarding.ts
Normal file
22
frontend/src/api/generated/model/agentOnboarding.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* OpenClaw Agency API
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface AgentOnboarding {
|
||||||
|
id?: number | null;
|
||||||
|
agent_name: string;
|
||||||
|
role_title: string;
|
||||||
|
prompt: string;
|
||||||
|
cron_interval_ms?: number | null;
|
||||||
|
tools_json?: string | null;
|
||||||
|
owner_hr_id?: number | null;
|
||||||
|
status?: string;
|
||||||
|
spawned_agent_id?: string | null;
|
||||||
|
session_key?: string | null;
|
||||||
|
notes?: string | null;
|
||||||
|
created_at?: string;
|
||||||
|
updated_at?: string;
|
||||||
|
}
|
||||||
19
frontend/src/api/generated/model/agentOnboardingCreate.ts
Normal file
19
frontend/src/api/generated/model/agentOnboardingCreate.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* OpenClaw Agency API
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface AgentOnboardingCreate {
|
||||||
|
agent_name: string;
|
||||||
|
role_title: string;
|
||||||
|
prompt: string;
|
||||||
|
cron_interval_ms?: number | null;
|
||||||
|
tools_json?: string | null;
|
||||||
|
owner_hr_id?: number | null;
|
||||||
|
status?: string;
|
||||||
|
spawned_agent_id?: string | null;
|
||||||
|
session_key?: string | null;
|
||||||
|
notes?: string | null;
|
||||||
|
}
|
||||||
19
frontend/src/api/generated/model/agentOnboardingUpdate.ts
Normal file
19
frontend/src/api/generated/model/agentOnboardingUpdate.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/**
|
||||||
|
* Generated by orval v8.2.0 🍺
|
||||||
|
* Do not edit manually.
|
||||||
|
* OpenClaw Agency API
|
||||||
|
* OpenAPI spec version: 0.3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface AgentOnboardingUpdate {
|
||||||
|
agent_name?: string | null;
|
||||||
|
role_title?: string | null;
|
||||||
|
prompt?: string | null;
|
||||||
|
cron_interval_ms?: number | null;
|
||||||
|
tools_json?: string | null;
|
||||||
|
owner_hr_id?: number | null;
|
||||||
|
status?: string | null;
|
||||||
|
spawned_agent_id?: string | null;
|
||||||
|
session_key?: string | null;
|
||||||
|
notes?: string | null;
|
||||||
|
}
|
||||||
@@ -5,6 +5,9 @@
|
|||||||
* OpenAPI spec version: 0.3.0
|
* OpenAPI spec version: 0.3.0
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export * from "./agentOnboarding";
|
||||||
|
export * from "./agentOnboardingCreate";
|
||||||
|
export * from "./agentOnboardingUpdate";
|
||||||
export * from "./department";
|
export * from "./department";
|
||||||
export * from "./departmentCreate";
|
export * from "./departmentCreate";
|
||||||
export * from "./departmentUpdate";
|
export * from "./departmentUpdate";
|
||||||
|
|||||||
@@ -1,3 +1,17 @@
|
|||||||
|
|
||||||
|
function getActorId(): string | undefined {
|
||||||
|
if (typeof window !== "undefined") {
|
||||||
|
const stored = window.localStorage.getItem("actor_employee_id");
|
||||||
|
if (stored) return stored;
|
||||||
|
const env = process.env.NEXT_PUBLIC_ACTOR_EMPLOYEE_ID;
|
||||||
|
if (env) {
|
||||||
|
window.localStorage.setItem("actor_employee_id", env);
|
||||||
|
return env;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return process.env.NEXT_PUBLIC_ACTOR_EMPLOYEE_ID;
|
||||||
|
}
|
||||||
export async function customFetch<T>(
|
export async function customFetch<T>(
|
||||||
url: string,
|
url: string,
|
||||||
options: RequestInit,
|
options: RequestInit,
|
||||||
@@ -9,6 +23,7 @@ export async function customFetch<T>(
|
|||||||
...options,
|
...options,
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json",
|
"Content-Type": "application/json",
|
||||||
|
...(getActorId() ? { "X-Actor-Employee-Id": String(getActorId()) } : {}),
|
||||||
...(options.headers ?? {}),
|
...(options.headers ?? {}),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { usePathname } from "next/navigation";
|
import { usePathname } from "next/navigation";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
import styles from "./Shell.module.css";
|
import styles from "./Shell.module.css";
|
||||||
|
|
||||||
const NAV = [
|
const NAV = [
|
||||||
@@ -14,6 +15,16 @@ const NAV = [
|
|||||||
|
|
||||||
export function Shell({ children }: { children: React.ReactNode }) {
|
export function Shell({ children }: { children: React.ReactNode }) {
|
||||||
const path = usePathname();
|
const path = usePathname();
|
||||||
|
const [actorId, setActorId] = useState("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
try {
|
||||||
|
const stored = window.localStorage.getItem("actor_employee_id");
|
||||||
|
if (stored) setActorId(stored);
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
return (
|
return (
|
||||||
<div className={styles.shell}>
|
<div className={styles.shell}>
|
||||||
<aside className={styles.sidebar}>
|
<aside className={styles.sidebar}>
|
||||||
@@ -32,6 +43,25 @@ export function Shell({ children }: { children: React.ReactNode }) {
|
|||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</nav>
|
</nav>
|
||||||
|
<div className={styles.mono} style={{ marginTop: 16 }}>
|
||||||
|
<div style={{ fontWeight: 600, marginBottom: 6 }}>Actor ID</div>
|
||||||
|
<input
|
||||||
|
value={actorId}
|
||||||
|
onChange={(e) => {
|
||||||
|
const v = e.target.value;
|
||||||
|
setActorId(v);
|
||||||
|
try {
|
||||||
|
if (v) window.localStorage.setItem("actor_employee_id", v);
|
||||||
|
else window.localStorage.removeItem("actor_employee_id");
|
||||||
|
} catch {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
placeholder="e.g. 1"
|
||||||
|
style={{ width: "100%", padding: "6px 8px", borderRadius: 6, border: "1px solid #333", background: "transparent" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className={styles.mono} style={{ marginTop: "auto" }}>
|
<div className={styles.mono} style={{ marginTop: "auto" }}>
|
||||||
Tip: use your machine IP + ports<br />
|
Tip: use your machine IP + ports<br />
|
||||||
<span className={styles.kbd}>:3000</span> UI <span className={styles.kbd}>:8000</span> API
|
<span className={styles.kbd}>:3000</span> UI <span className={styles.kbd}>:8000</span> API
|
||||||
|
|||||||
@@ -13,6 +13,9 @@ import {
|
|||||||
useCreateEmploymentActionHrActionsPost,
|
useCreateEmploymentActionHrActionsPost,
|
||||||
useListHeadcountRequestsHrHeadcountGet,
|
useListHeadcountRequestsHrHeadcountGet,
|
||||||
useListEmploymentActionsHrActionsGet,
|
useListEmploymentActionsHrActionsGet,
|
||||||
|
useListAgentOnboardingHrOnboardingGet,
|
||||||
|
useCreateAgentOnboardingHrOnboardingPost,
|
||||||
|
useUpdateAgentOnboardingHrOnboardingOnboardingIdPatch,
|
||||||
} from "@/api/generated/hr/hr";
|
} from "@/api/generated/hr/hr";
|
||||||
import { useListDepartmentsDepartmentsGet, useListEmployeesEmployeesGet } from "@/api/generated/org/org";
|
import { useListDepartmentsDepartmentsGet, useListEmployeesEmployeesGet } from "@/api/generated/org/org";
|
||||||
|
|
||||||
@@ -35,6 +38,14 @@ export default function HRPage() {
|
|||||||
const [actType, setActType] = useState("praise");
|
const [actType, setActType] = useState("praise");
|
||||||
const [actNotes, setActNotes] = useState("");
|
const [actNotes, setActNotes] = useState("");
|
||||||
|
|
||||||
|
|
||||||
|
const [onboardAgentName, setOnboardAgentName] = useState("");
|
||||||
|
const [onboardRole, setOnboardRole] = useState("");
|
||||||
|
const [onboardPrompt, setOnboardPrompt] = useState("");
|
||||||
|
const [onboardCronMs, setOnboardCronMs] = useState("");
|
||||||
|
const [onboardTools, setOnboardTools] = useState("");
|
||||||
|
const [onboardOwnerId, setOnboardOwnerId] = useState<string>("");
|
||||||
|
const [onboardNotes, setOnboardNotes] = useState("");
|
||||||
const createHeadcount = useCreateHeadcountRequestHrHeadcountPost({
|
const createHeadcount = useCreateHeadcountRequestHrHeadcountPost({
|
||||||
mutation: {
|
mutation: {
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
@@ -200,6 +211,100 @@ export default function HRPage() {
|
|||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
||||||
|
<div className="mt-6 grid gap-4">
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle>Agent onboarding</CardTitle>
|
||||||
|
<CardDescription>HR logs prompts, cron, tools, and spawn status (Mission Control only).</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="grid gap-4 sm:grid-cols-2">
|
||||||
|
<div className="space-y-3">
|
||||||
|
<Input placeholder="Agent name" value={onboardAgentName} onChange={(e) => setOnboardAgentName(e.target.value)} />
|
||||||
|
<Input placeholder="Role/title" value={onboardRole} onChange={(e) => setOnboardRole(e.target.value)} />
|
||||||
|
<Textarea placeholder="Prompt / system instructions" value={onboardPrompt} onChange={(e) => setOnboardPrompt(e.target.value)} />
|
||||||
|
<Input placeholder="Cron interval ms (e.g. 300000)" value={onboardCronMs} onChange={(e) => setOnboardCronMs(e.target.value)} />
|
||||||
|
<Textarea placeholder="Tools/permissions (JSON or text)" value={onboardTools} onChange={(e) => setOnboardTools(e.target.value)} />
|
||||||
|
<Select value={onboardOwnerId} onChange={(e) => setOnboardOwnerId(e.target.value)}>
|
||||||
|
<option value="">Owner (HR)</option>
|
||||||
|
{(employees.data ?? []).map((e) => (
|
||||||
|
<option key={e.id ?? e.name} value={e.id ?? ""}>{e.name}</option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
|
<Textarea placeholder="Notes" value={onboardNotes} onChange={(e) => setOnboardNotes(e.target.value)} />
|
||||||
|
<Button
|
||||||
|
onClick={() =>
|
||||||
|
createOnboarding.mutate({
|
||||||
|
data: {
|
||||||
|
agent_name: onboardAgentName,
|
||||||
|
role_title: onboardRole,
|
||||||
|
prompt: onboardPrompt,
|
||||||
|
cron_interval_ms: onboardCronMs ? Number(onboardCronMs) : null,
|
||||||
|
tools_json: onboardTools.trim() ? onboardTools : null,
|
||||||
|
owner_hr_id: onboardOwnerId ? Number(onboardOwnerId) : null,
|
||||||
|
status: "planned",
|
||||||
|
notes: onboardNotes.trim() ? onboardNotes : null,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
disabled={!onboardAgentName.trim() || !onboardRole.trim() || !onboardPrompt.trim() || createOnboarding.isPending}
|
||||||
|
>
|
||||||
|
Create onboarding
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div className="mb-2 text-sm font-medium">Current onboardings</div>
|
||||||
|
<ul className="space-y-2">
|
||||||
|
{(onboarding.data ?? []).map((o) => (
|
||||||
|
<li key={String(o.id)} className="rounded-md border p-3 text-sm">
|
||||||
|
<div className="font-medium">{o.agent_name} · {o.role_title}</div>
|
||||||
|
<div className="text-xs text-muted-foreground">status: {o.status} · cron: {o.cron_interval_ms ?? "—"}</div>
|
||||||
|
<div className="mt-2 grid gap-2">
|
||||||
|
<Select
|
||||||
|
value={o.status ?? ""}
|
||||||
|
onChange={(e) =>
|
||||||
|
updateOnboarding.mutate({ onboardingId: Number(o.id), data: { status: e.target.value || null } })
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<option value="planned">planned</option>
|
||||||
|
<option value="spawning">spawning</option>
|
||||||
|
<option value="spawned">spawned</option>
|
||||||
|
<option value="verified">verified</option>
|
||||||
|
<option value="blocked">blocked</option>
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
placeholder="Spawned agent id"
|
||||||
|
defaultValue={o.spawned_agent_id ?? ""}
|
||||||
|
onBlur={(e) =>
|
||||||
|
updateOnboarding.mutate({ onboardingId: Number(o.id), data: { spawned_agent_id: e.currentTarget.value || null } })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
placeholder="Session key"
|
||||||
|
defaultValue={o.session_key ?? ""}
|
||||||
|
onBlur={(e) =>
|
||||||
|
updateOnboarding.mutate({ onboardingId: Number(o.id), data: { session_key: e.currentTarget.value || null } })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Textarea
|
||||||
|
placeholder="Notes"
|
||||||
|
defaultValue={o.notes ?? ""}
|
||||||
|
onBlur={(e) =>
|
||||||
|
updateOnboarding.mutate({ onboardingId: Number(o.id), data: { notes: e.currentTarget.value || null } })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
{(onboarding.data ?? []).length === 0 ? (
|
||||||
|
<li className="text-sm text-muted-foreground">No onboarding records yet.</li>
|
||||||
|
) : null}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user