Source code for zoho_people.api.timesheet

"""
Timesheet API – gestione timesheet e time log in Zoho People.

Endpoint coperti:
  GET  /api/timetracker/gettimesheet           – lista timesheet
  GET  /api/timetracker/gettimesheetdetails    – dettaglio timesheet
  POST /api/timetracker/createtimesheet        – crea timesheet
  POST /api/timetracker/modifytimesheet        – modifica / invia per approvazione
  POST /api/timetracker/deletetimesheet        – elimina timesheet
  POST /api/timetracker/approvetimesheet       – approva / rifiuta timesheet
  GET  /api/timetracker/gettimelogdetails      – dettaglio time log
  POST /api/timetracker/addtimelog             – aggiungi time log
  POST /api/timetracker/edittimelog            – modifica time log
  POST /api/timetracker/deletetimelog          – elimina time log
  GET  /api/timetracker/getpayrollreport       – report payroll
  GET  /api/timetracker/gettimetrackersettings – impostazioni generali

Scope richiesto: ZOHOPEOPLE.timetracker.ALL
Rate limit: 20–50 req/min a seconda dell'endpoint.
"""

from __future__ import annotations

from typing import TYPE_CHECKING, Any, Optional

if TYPE_CHECKING:
    from ..client import ZohoPeopleClient


[docs] class TimesheetAPI: """ Gestione completa di timesheet e time log Zoho People. Example ------- >>> # Lista timesheet approvati di aprile >>> sheets = client.timesheet.list( ... user="mario.rossi@azienda.it", ... from_date="01-Apr-2026", ... to_date="30-Apr-2026", ... approval_status="approved", ... ) >>> # Crea un timesheet settimanale >>> result = client.timesheet.create( ... user="mario.rossi@azienda.it", ... name="Settimana 17/2026", ... from_date="20-04-2026", ... to_date="26-04-2026", ... send_for_approval=True, ... ) >>> # Aggiungi ore a un timesheet >>> client.timesheet.add_timelog( ... user="mario.rossi@azienda.it", ... work_date="2026-04-25", ... hours="08:00", ... job_name="Sviluppo SDK", ... billing_status="Billable", ... ) """ def __init__(self, client: "ZohoPeopleClient"): self._client = client # ------------------------------------------------------------------ # Timesheet – lettura # ------------------------------------------------------------------
[docs] def list( self, user: str, from_date: Optional[str] = None, to_date: Optional[str] = None, approval_status: str = "all", employee_status: str = "users", date_format: Optional[str] = None, s_index: int = 0, limit: int = 200, ) -> list[dict]: """ Recupera la lista dei timesheet. Parameters ---------- user : str Email, Employee ID, erecno o ``"all"`` per tutti. from_date : str, optional Data inizio (nel formato aziendale o in ``date_format``). to_date : str, optional Data fine. approval_status : str ``all`` | ``draft`` | ``pending`` | ``approved`` | ``rejected`` employee_status : str ``users`` | ``nonusers`` | ``usersandnonusers`` | ``logindisabled`` s_index : int Indice di inizio per la paginazione (default 0). limit : int Numero di record per pagina (max 200). Returns ------- list[dict] Lista di timesheet con campi: recordId, timesheetName, status, totalHours, billableHours, fromDate, toDate, … """ params: dict[str, Any] = { "user": user, "approvalStatus": approval_status, "employeeStatus": employee_status, "sIndex": s_index, "limit": min(limit, 200), } if from_date: params["fromDate"] = from_date if to_date: params["toDate"] = to_date if date_format: params["dateFormat"] = date_format result = self._client.get("timetracker/gettimesheet", params=params) return result.get("result", []) if isinstance(result, dict) else []
[docs] def get_all(self, user: str, **kwargs) -> list[dict]: """ Auto-paginazione: recupera TUTTI i timesheet dell'utente. """ all_sheets: list[dict] = [] index = 0 limit = 200 while True: batch = self.list(user=user, s_index=index, limit=limit, **kwargs) if not batch: break all_sheets.extend(batch) if len(batch) < limit: break index += limit return all_sheets
[docs] def get_detail(self, timesheet_id: str) -> dict: """ Recupera il dettaglio di un timesheet (time log inclusi). Parameters ---------- timesheet_id : str ID del timesheet (``recordId`` dalla lista). Returns ------- dict Dettaglio completo con time log, progetti, clienti. """ params = {"timesheetId": timesheet_id} result = self._client.get("timetracker/gettimesheetdetails", params=params) return result.get("result", result) if isinstance(result, dict) else {}
# ------------------------------------------------------------------ # Timesheet – scrittura # ------------------------------------------------------------------
[docs] def create( self, user: str, name: str, from_date: str, to_date: str, description: Optional[str] = None, billable_status: str = "all", job_id: str = "all", project_id: str = "all", client_id: str = "all", send_for_approval: bool = False, date_format: Optional[str] = None, ) -> dict: """ Crea un nuovo timesheet. Parameters ---------- user : str Email o Employee ID del dipendente. name : str Nome del timesheet (es. ``"Settimana 17/2026"``). from_date : str Data inizio. to_date : str Data fine. description : str, optional Descrizione del timesheet. billable_status : str ``all`` | ``Billable`` | ``Non Billable`` send_for_approval : bool Se True, invia subito per approvazione. Returns ------- dict ``{"timesheetId": [...], "message": "..."}`` """ params: dict[str, Any] = { "user": user, "timesheetName": name, "fromDate": from_date, "toDate": to_date, "billableStatus": billable_status, "jobId": job_id, "projectId": project_id, "clientId": client_id, "sendforApproval": str(send_for_approval).lower(), } if description: params["description"] = description if date_format: params["dateFormat"] = date_format result = self._client.post("timetracker/createtimesheet", data=params) return result.get("result", result) if isinstance(result, dict) else {}
[docs] def modify( self, timesheet_id: str, name: Optional[str] = None, description: Optional[str] = None, send_for_approval: Optional[bool] = None, ) -> dict: """ Modifica un timesheet esistente o lo invia per approvazione. Per ri-sottomettere un timesheet rifiutato usare ``send_for_approval=True``. Returns ------- dict ``{"timesheetId": "...", "message": "..."}`` """ params: dict[str, Any] = {"timesheetId": timesheet_id} if name: params["timesheetName"] = name if description is not None: params["description"] = description if send_for_approval is not None: params["sendforApproval"] = str(send_for_approval).lower() result = self._client.post("timetracker/modifytimesheet", data=params) return result.get("result", result) if isinstance(result, dict) else {}
[docs] def delete(self, timesheet_id: str) -> dict: """ Elimina un timesheet. Returns ------- dict Risposta di conferma. """ params = {"timesheetId": timesheet_id} result = self._client.post("timetracker/deletetimesheet", data=params) return result.get("result", result) if isinstance(result, dict) else {}
[docs] def approve( self, timesheet_id: str, approval_status: str = "approved", time_logs: Optional[dict] = None, comments: Optional[str] = None, all_levels: bool = False, ) -> dict: """ Approva o rifiuta un timesheet (e opzionalmente i suoi time log). Parameters ---------- timesheet_id : str ID del timesheet. approval_status : str ``"approved"`` | ``"rejected"`` time_logs : dict, optional Dict ``{timelog_id: "approved"|"rejected"}`` per approvazione parziale. Es: ``{"469505000000272225": "approved", "469505000000272083": "rejected"}`` comments : str, optional Commento per l'approvazione/rifiuto. all_levels : bool Se True, approva a tutti i livelli gerarchici. Returns ------- dict ``{"timesheetId": "...", "message": "..."}`` """ import json as _json params: dict[str, Any] = { "timesheetId": timesheet_id, "approvalStatus": approval_status, "isAllLevelApprove": str(all_levels).lower(), } if time_logs: params["timeLogs"] = _json.dumps(time_logs) if comments: params["comments"] = comments result = self._client.post("timetracker/approvetimesheet", data=params) return result.get("result", result) if isinstance(result, dict) else {}
# ------------------------------------------------------------------ # Time Log – lettura # ------------------------------------------------------------------
[docs] def get_timelog(self, timelog_id: str) -> dict: """ Recupera il dettaglio di un singolo time log. Returns ------- dict Dettaglio con: workDate, hours, jobName, billingStatus, … """ params = {"timeLogId": timelog_id} result = self._client.get("timetracker/gettimelogdetails", params=params) items = result.get("result", []) if isinstance(result, dict) else [] return items[0] if items else {}
# ------------------------------------------------------------------ # Time Log – scrittura # ------------------------------------------------------------------
[docs] def add_timelog( self, user: str, work_date: str, hours: str, job_id: Optional[str] = None, job_name: Optional[str] = None, billing_status: str = "Billable", work_item: Optional[str] = None, description: Optional[str] = None, ) -> dict: """ Aggiunge un time log a un timesheet. Almeno uno tra ``job_id`` e ``job_name`` è obbligatorio. Parameters ---------- user : str Email o Employee ID del dipendente. work_date : str Data di lavoro nel formato ``YYYY-MM-DD``. hours : str Ore lavorate nel formato ``HH:MM`` (es. ``"08:00"``). job_id : str, optional ID del job/attività. job_name : str, optional Nome del job/attività (alternativa a job_id). billing_status : str ``"Billable"`` | ``"Non Billable"`` work_item : str, optional Sottoattività / work item. description : str, optional Descrizione delle ore lavorate. Returns ------- dict ``{"timeLogId": "...", "message": "..."}`` """ params: dict[str, Any] = { "user": user, "workDate": work_date, "hours": hours, "billingStatus": billing_status, } if job_id: params["jobId"] = job_id if job_name: params["jobName"] = job_name if work_item: params["workItem"] = work_item if description: params["description"] = description result = self._client.post("timetracker/addtimelog", data=params) items = result.get("result", []) if isinstance(result, dict) else [] return items[0] if items else {}
[docs] def edit_timelog( self, timelog_id: str, hours: Optional[str] = None, work_date: Optional[str] = None, billing_status: Optional[str] = None, description: Optional[str] = None, ) -> dict: """ Modifica un time log esistente. Returns ------- dict Risposta di conferma. """ params: dict[str, Any] = {"timeLogId": timelog_id} if hours: params["hours"] = hours if work_date: params["workDate"] = work_date if billing_status: params["billingStatus"] = billing_status if description is not None: params["description"] = description result = self._client.post("timetracker/edittimelog", data=params) return result.get("result", result) if isinstance(result, dict) else {}
[docs] def delete_timelog(self, timelog_id: str) -> dict: """ Elimina un time log. Returns ------- dict Risposta di conferma. """ params = {"timeLogId": timelog_id} result = self._client.post("timetracker/deletetimelog", data=params) return result.get("result", result) if isinstance(result, dict) else {}
# ------------------------------------------------------------------ # Report e impostazioni # ------------------------------------------------------------------
[docs] def get_payroll_report( self, user: str, from_date: str, to_date: str, date_format: Optional[str] = None, ) -> list[dict]: """ Recupera il report payroll (ore ordinarie, straordinari, ferie retribuite). Parameters ---------- user : str Email, Employee ID, erecno o ``"all"``. from_date : str Data inizio. to_date : str Data fine. Returns ------- list[dict] Lista di record con: regularHour, OtHours, paidLeaveHours, totalAmount, … """ params: dict[str, Any] = { "user": user, "fromDate": from_date, "toDate": to_date, } if date_format: params["dateFormat"] = date_format result = self._client.get("timetracker/getpayrollreport", params=params) return result.get("result", []) if isinstance(result, dict) else []
[docs] def get_settings(self) -> dict: """ Recupera le impostazioni generali del time tracker. Returns ------- dict Impostazioni: tipo log (ore/inizio-fine/timer), max ore/giorno, … """ result = self._client.get("timetracker/gettimetrackersettings") return result.get("result", result) if isinstance(result, dict) else {}
[docs] def get_jobs( self, assigned_to: Optional[str] = None, job_status: str = "in-progress", s_index: int = 0, limit: int = 200, ) -> list[dict]: """ Recupera la lista dei job disponibili. Parameters ---------- assigned_to : str, optional Email o Employee ID del dipendente. Se omesso → tutti i job. job_status : str ``"in-progress"`` (default) | ``"completed"`` | ``"all"`` s_index : int Indice di partenza per paginazione. limit : int Max record (default 200). Returns ------- list[dict] Lista di job con: jobId, jobName, projectName, clientName, jobStatus, jobBillableStatus, fromDate, toDate. """ params: dict[str, Any] = { "jobStatus": job_status, "sIndex": s_index, "limit": min(limit, 200), } if assigned_to: params["assignedTo"] = assigned_to result = self._client.get("timetracker/getjobs", params=params) return result.get("result", []) if isinstance(result, dict) else []