Skip to main content

datasynth_core/models/
time_entry.rs

1//! Time entry models for the Hire-to-Retire (H2R) process.
2//!
3//! These models represent employee time tracking entries including regular hours,
4//! overtime, PTO, and sick leave with an approval workflow.
5
6use chrono::NaiveDate;
7use serde::{Deserialize, Serialize};
8use std::collections::HashMap;
9
10use super::graph_properties::{GraphPropertyValue, ToNodeProperties};
11
12/// Approval status of a time entry.
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
14#[serde(rename_all = "snake_case")]
15pub enum TimeApprovalStatus {
16    /// Awaiting manager approval
17    #[default]
18    Pending,
19    /// Approved by manager
20    Approved,
21    /// Rejected by manager
22    Rejected,
23}
24
25/// A time entry recording hours worked or leave taken by an employee.
26#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct TimeEntry {
28    /// Unique time entry identifier
29    pub entry_id: String,
30    /// Employee who recorded the time
31    pub employee_id: String,
32    /// Date the time entry applies to
33    pub date: NaiveDate,
34    /// Regular hours worked
35    pub hours_regular: f64,
36    /// Overtime hours worked
37    pub hours_overtime: f64,
38    /// Paid time off hours used
39    pub hours_pto: f64,
40    /// Sick leave hours used
41    pub hours_sick: f64,
42    /// Project the time was charged to
43    pub project_id: Option<String>,
44    /// Cost center allocation
45    pub cost_center: Option<String>,
46    /// Description of work performed
47    pub description: Option<String>,
48    /// Current approval status
49    pub approval_status: TimeApprovalStatus,
50    /// Manager who approved/rejected the entry
51    pub approved_by: Option<String>,
52    /// Date the entry was submitted for approval
53    pub submitted_at: Option<NaiveDate>,
54    /// Employee display name (denormalized, DS-011)
55    #[serde(default, skip_serializing_if = "Option::is_none")]
56    pub employee_name: Option<String>,
57    /// Whether this time is billable (DS-012)
58    #[serde(default)]
59    pub billable: bool,
60}
61
62impl ToNodeProperties for TimeEntry {
63    fn node_type_name(&self) -> &'static str {
64        "time_entry"
65    }
66    fn node_type_code(&self) -> u16 {
67        331
68    }
69    fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
70        let mut p = HashMap::new();
71        p.insert(
72            "entryId".into(),
73            GraphPropertyValue::String(self.entry_id.clone()),
74        );
75        p.insert(
76            "employeeId".into(),
77            GraphPropertyValue::String(self.employee_id.clone()),
78        );
79        if let Some(ref name) = self.employee_name {
80            p.insert(
81                "employeeName".into(),
82                GraphPropertyValue::String(name.clone()),
83            );
84        }
85        p.insert("date".into(), GraphPropertyValue::Date(self.date));
86        p.insert(
87            "hours".into(),
88            GraphPropertyValue::Float(self.hours_regular + self.hours_overtime),
89        );
90        p.insert(
91            "hoursRegular".into(),
92            GraphPropertyValue::Float(self.hours_regular),
93        );
94        p.insert(
95            "hoursOvertime".into(),
96            GraphPropertyValue::Float(self.hours_overtime),
97        );
98        p.insert("billable".into(), GraphPropertyValue::Bool(self.billable));
99        p.insert(
100            "status".into(),
101            GraphPropertyValue::String(format!("{:?}", self.approval_status)),
102        );
103        if let Some(ref proj) = self.project_id {
104            p.insert("projectId".into(), GraphPropertyValue::String(proj.clone()));
105        }
106        p
107    }
108}