datasynth_core/models/
expense_report.rs1use chrono::NaiveDate;
7use rust_decimal::Decimal;
8use serde::{Deserialize, Serialize};
9use smallvec::SmallVec;
10use std::collections::HashMap;
11
12use super::graph_properties::{GraphPropertyValue, ToNodeProperties};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
16#[serde(rename_all = "snake_case")]
17pub enum ExpenseStatus {
18 #[default]
20 Draft,
21 Submitted,
23 Approved,
25 Rejected,
27 Paid,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33#[serde(rename_all = "snake_case")]
34pub enum ExpenseCategory {
35 Travel,
37 Meals,
39 Lodging,
41 Transportation,
43 Office,
45 Entertainment,
47 Training,
49 Other,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct ExpenseReport {
56 pub report_id: String,
58 pub employee_id: String,
60 pub submission_date: NaiveDate,
62 pub description: String,
64 pub status: ExpenseStatus,
66 #[serde(with = "rust_decimal::serde::str")]
68 pub total_amount: Decimal,
69 pub currency: String,
71 pub line_items: SmallVec<[ExpenseLineItem; 4]>,
73 pub approved_by: Option<String>,
75 pub approved_date: Option<NaiveDate>,
77 pub paid_date: Option<NaiveDate>,
79 pub cost_center: Option<String>,
81 pub department: Option<String>,
83 pub policy_violations: Vec<String>,
85 #[serde(default, skip_serializing_if = "Option::is_none")]
87 pub employee_name: Option<String>,
88}
89
90impl ToNodeProperties for ExpenseReport {
91 fn node_type_name(&self) -> &'static str {
92 "expense_report"
93 }
94 fn node_type_code(&self) -> u16 {
95 332
96 }
97 fn to_node_properties(&self) -> HashMap<String, GraphPropertyValue> {
98 let mut p = HashMap::new();
99 p.insert(
100 "reportId".into(),
101 GraphPropertyValue::String(self.report_id.clone()),
102 );
103 p.insert(
104 "employeeId".into(),
105 GraphPropertyValue::String(self.employee_id.clone()),
106 );
107 if let Some(ref name) = self.employee_name {
108 p.insert(
109 "employeeName".into(),
110 GraphPropertyValue::String(name.clone()),
111 );
112 }
113 p.insert(
114 "submissionDate".into(),
115 GraphPropertyValue::Date(self.submission_date),
116 );
117 p.insert(
118 "totalAmount".into(),
119 GraphPropertyValue::Decimal(self.total_amount),
120 );
121 p.insert(
122 "currency".into(),
123 GraphPropertyValue::String(self.currency.clone()),
124 );
125 p.insert(
126 "lineCount".into(),
127 GraphPropertyValue::Int(self.line_items.len() as i64),
128 );
129 p.insert(
130 "status".into(),
131 GraphPropertyValue::String(format!("{:?}", self.status)),
132 );
133 p.insert(
134 "isApproved".into(),
135 GraphPropertyValue::Bool(matches!(
136 self.status,
137 ExpenseStatus::Approved | ExpenseStatus::Paid
138 )),
139 );
140 if let Some(ref dept) = self.department {
141 p.insert(
142 "department".into(),
143 GraphPropertyValue::String(dept.clone()),
144 );
145 }
146 p
147 }
148}
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct ExpenseLineItem {
153 pub item_id: String,
155 pub category: ExpenseCategory,
157 pub date: NaiveDate,
159 #[serde(with = "rust_decimal::serde::str")]
161 pub amount: Decimal,
162 pub currency: String,
164 pub description: String,
166 pub receipt_attached: bool,
168 pub merchant: Option<String>,
170}