backbone_payroll/domain/entity/
salary_slip.rs1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6use super::AuditMetadata;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
10#[serde(transparent)]
11pub struct SalarySlipId(pub Uuid);
12
13impl SalarySlipId {
14 pub fn new(id: Uuid) -> Self { Self(id) }
15 pub fn generate() -> Self { Self(Uuid::new_v4()) }
16 pub fn into_inner(self) -> Uuid { self.0 }
17}
18
19impl std::fmt::Display for SalarySlipId {
20 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21 write!(f, "{}", self.0)
22 }
23}
24
25impl std::str::FromStr for SalarySlipId {
26 type Err = uuid::Error;
27 fn from_str(s: &str) -> Result<Self, Self::Err> {
28 Ok(Self(Uuid::parse_str(s)?))
29 }
30}
31
32impl From<Uuid> for SalarySlipId {
33 fn from(id: Uuid) -> Self { Self(id) }
34}
35
36impl From<SalarySlipId> for Uuid {
37 fn from(id: SalarySlipId) -> Self { id.0 }
38}
39
40impl AsRef<Uuid> for SalarySlipId {
41 fn as_ref(&self) -> &Uuid { &self.0 }
42}
43
44impl std::ops::Deref for SalarySlipId {
45 type Target = Uuid;
46 fn deref(&self) -> &Self::Target { &self.0 }
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
50pub struct SalarySlip {
51 pub id: Uuid,
52 pub payroll_entry_id: Uuid,
53 pub employee_id: Uuid,
54 pub structure_id: Option<Uuid>,
55 pub working_days: Decimal,
56 pub unpaid_days: Decimal,
57 pub gross_pay: Decimal,
58 pub total_deductions: Decimal,
59 pub net_pay: Decimal,
60 pub overtime_hours: Option<Decimal>,
61 pub tax_method: Option<String>,
62 #[serde(default)]
63 #[sqlx(json)]
64 pub metadata: AuditMetadata,
65}
66
67impl SalarySlip {
68 pub fn builder() -> SalarySlipBuilder {
70 <SalarySlipBuilder as Default>::default()
71 }
72
73 pub fn new(payroll_entry_id: Uuid, employee_id: Uuid, working_days: Decimal, unpaid_days: Decimal, gross_pay: Decimal, total_deductions: Decimal, net_pay: Decimal) -> Self {
75 Self {
76 id: Uuid::new_v4(),
77 payroll_entry_id,
78 employee_id,
79 structure_id: None,
80 working_days,
81 unpaid_days,
82 gross_pay,
83 total_deductions,
84 net_pay,
85 overtime_hours: None,
86 tax_method: None,
87 metadata: AuditMetadata::default(),
88 }
89 }
90
91 pub fn id(&self) -> &Uuid {
93 &self.id
94 }
95
96 pub fn typed_id(&self) -> SalarySlipId {
98 SalarySlipId(self.id)
99 }
100
101 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
103 self.metadata.created_at.as_ref()
104 }
105
106 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
108 self.metadata.updated_at.as_ref()
109 }
110
111 pub fn is_deleted(&self) -> bool {
113 self.metadata.deleted_at.is_some()
114 }
115
116 pub fn is_active(&self) -> bool {
118 self.metadata.deleted_at.is_none()
119 }
120
121 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
123 self.metadata.deleted_at.as_ref()
124 }
125
126 pub fn created_by(&self) -> Option<&Uuid> {
128 self.metadata.created_by.as_ref()
129 }
130
131 pub fn updated_by(&self) -> Option<&Uuid> {
133 self.metadata.updated_by.as_ref()
134 }
135
136 pub fn deleted_by(&self) -> Option<&Uuid> {
138 self.metadata.deleted_by.as_ref()
139 }
140
141
142 pub fn with_structure_id(mut self, value: Uuid) -> Self {
148 self.structure_id = Some(value);
149 self
150 }
151
152 pub fn with_overtime_hours(mut self, value: Decimal) -> Self {
154 self.overtime_hours = Some(value);
155 self
156 }
157
158 pub fn with_tax_method(mut self, value: String) -> Self {
160 self.tax_method = Some(value);
161 self
162 }
163
164 pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
170 for (key, value) in fields {
171 match key.as_str() {
172 "payroll_entry_id" => {
173 if let Ok(v) = serde_json::from_value(value) { self.payroll_entry_id = v; }
174 }
175 "employee_id" => {
176 if let Ok(v) = serde_json::from_value(value) { self.employee_id = v; }
177 }
178 "structure_id" => {
179 if let Ok(v) = serde_json::from_value(value) { self.structure_id = v; }
180 }
181 "working_days" => {
182 if let Ok(v) = serde_json::from_value(value) { self.working_days = v; }
183 }
184 "unpaid_days" => {
185 if let Ok(v) = serde_json::from_value(value) { self.unpaid_days = v; }
186 }
187 "gross_pay" => {
188 if let Ok(v) = serde_json::from_value(value) { self.gross_pay = v; }
189 }
190 "total_deductions" => {
191 if let Ok(v) = serde_json::from_value(value) { self.total_deductions = v; }
192 }
193 "net_pay" => {
194 if let Ok(v) = serde_json::from_value(value) { self.net_pay = v; }
195 }
196 "overtime_hours" => {
197 if let Ok(v) = serde_json::from_value(value) { self.overtime_hours = v; }
198 }
199 "tax_method" => {
200 if let Ok(v) = serde_json::from_value(value) { self.tax_method = v; }
201 }
202 _ => {} }
204 }
205 }
206
207 }
210
211impl super::Entity for SalarySlip {
212 type Id = Uuid;
213
214 fn entity_id(&self) -> &Self::Id {
215 &self.id
216 }
217
218 fn entity_type() -> &'static str {
219 "SalarySlip"
220 }
221}
222
223impl backbone_core::PersistentEntity for SalarySlip {
224 fn entity_id(&self) -> String {
225 self.id.to_string()
226 }
227 fn set_entity_id(&mut self, id: String) {
228 if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
229 self.id = uuid;
230 }
231 }
232 fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
233 self.metadata.created_at
234 }
235 fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
236 self.metadata.created_at = Some(ts);
237 }
238 fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
239 self.metadata.updated_at
240 }
241 fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
242 self.metadata.updated_at = Some(ts);
243 }
244 fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
245 self.metadata.deleted_at
246 }
247 fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
248 self.metadata.deleted_at = ts;
249 }
250}
251
252impl backbone_orm::EntityRepoMeta for SalarySlip {
253 fn column_types() -> std::collections::HashMap<String, String> {
254 let mut m = std::collections::HashMap::new();
255 m.insert("id".to_string(), "uuid".to_string());
256 m.insert("payroll_entry_id".to_string(), "uuid".to_string());
257 m.insert("employee_id".to_string(), "uuid".to_string());
258 m.insert("structure_id".to_string(), "uuid".to_string());
259 m
260 }
261 fn search_fields() -> &'static [&'static str] {
262 &[]
263 }
264}
265
266#[derive(Debug, Clone, Default)]
271pub struct SalarySlipBuilder {
272 payroll_entry_id: Option<Uuid>,
273 employee_id: Option<Uuid>,
274 structure_id: Option<Uuid>,
275 working_days: Option<Decimal>,
276 unpaid_days: Option<Decimal>,
277 gross_pay: Option<Decimal>,
278 total_deductions: Option<Decimal>,
279 net_pay: Option<Decimal>,
280 overtime_hours: Option<Decimal>,
281 tax_method: Option<String>,
282}
283
284impl SalarySlipBuilder {
285 pub fn payroll_entry_id(mut self, value: Uuid) -> Self {
287 self.payroll_entry_id = Some(value);
288 self
289 }
290
291 pub fn employee_id(mut self, value: Uuid) -> Self {
293 self.employee_id = Some(value);
294 self
295 }
296
297 pub fn structure_id(mut self, value: Uuid) -> Self {
299 self.structure_id = Some(value);
300 self
301 }
302
303 pub fn working_days(mut self, value: Decimal) -> Self {
305 self.working_days = Some(value);
306 self
307 }
308
309 pub fn unpaid_days(mut self, value: Decimal) -> Self {
311 self.unpaid_days = Some(value);
312 self
313 }
314
315 pub fn gross_pay(mut self, value: Decimal) -> Self {
317 self.gross_pay = Some(value);
318 self
319 }
320
321 pub fn total_deductions(mut self, value: Decimal) -> Self {
323 self.total_deductions = Some(value);
324 self
325 }
326
327 pub fn net_pay(mut self, value: Decimal) -> Self {
329 self.net_pay = Some(value);
330 self
331 }
332
333 pub fn overtime_hours(mut self, value: Decimal) -> Self {
335 self.overtime_hours = Some(value);
336 self
337 }
338
339 pub fn tax_method(mut self, value: String) -> Self {
341 self.tax_method = Some(value);
342 self
343 }
344
345 pub fn build(self) -> Result<SalarySlip, String> {
349 let payroll_entry_id = self.payroll_entry_id.ok_or_else(|| "payroll_entry_id is required".to_string())?;
350 let employee_id = self.employee_id.ok_or_else(|| "employee_id is required".to_string())?;
351
352 Ok(SalarySlip {
353 id: Uuid::new_v4(),
354 payroll_entry_id,
355 employee_id,
356 structure_id: self.structure_id,
357 working_days: self.working_days.unwrap_or(Decimal::from(0)),
358 unpaid_days: self.unpaid_days.unwrap_or(Decimal::from(0)),
359 gross_pay: self.gross_pay.unwrap_or(Decimal::from(0)),
360 total_deductions: self.total_deductions.unwrap_or(Decimal::from(0)),
361 net_pay: self.net_pay.unwrap_or(Decimal::from(0)),
362 overtime_hours: self.overtime_hours,
363 tax_method: self.tax_method,
364 metadata: AuditMetadata::default(),
365 })
366 }
367}