Skip to main content

backbone_payroll/domain/entity/
salary_slip_line.rs

1use chrono::{DateTime, Utc};
2use serde::{Deserialize, Serialize};
3use sqlx::FromRow;
4use uuid::Uuid;
5use rust_decimal::Decimal;
6
7use super::ComponentType;
8use super::AuditMetadata;
9
10/// Strongly-typed ID for SalarySlipLine
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
12#[serde(transparent)]
13pub struct SalarySlipLineId(pub Uuid);
14
15impl SalarySlipLineId {
16    pub fn new(id: Uuid) -> Self { Self(id) }
17    pub fn generate() -> Self { Self(Uuid::new_v4()) }
18    pub fn into_inner(self) -> Uuid { self.0 }
19}
20
21impl std::fmt::Display for SalarySlipLineId {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "{}", self.0)
24    }
25}
26
27impl std::str::FromStr for SalarySlipLineId {
28    type Err = uuid::Error;
29    fn from_str(s: &str) -> Result<Self, Self::Err> {
30        Ok(Self(Uuid::parse_str(s)?))
31    }
32}
33
34impl From<Uuid> for SalarySlipLineId {
35    fn from(id: Uuid) -> Self { Self(id) }
36}
37
38impl From<SalarySlipLineId> for Uuid {
39    fn from(id: SalarySlipLineId) -> Self { id.0 }
40}
41
42impl AsRef<Uuid> for SalarySlipLineId {
43    fn as_ref(&self) -> &Uuid { &self.0 }
44}
45
46impl std::ops::Deref for SalarySlipLineId {
47    type Target = Uuid;
48    fn deref(&self) -> &Self::Target { &self.0 }
49}
50
51#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
52pub struct SalarySlipLine {
53    pub id: Uuid,
54    pub salary_slip_id: Uuid,
55    pub name: String,
56    pub component_type: ComponentType,
57    pub is_statutory: bool,
58    pub amount: Decimal,
59    pub gl_account_id: Uuid,
60    pub source_kind: Option<String>,
61    pub source_ref: Option<Uuid>,
62    #[serde(default)]
63    #[sqlx(json)]
64    pub metadata: AuditMetadata,
65}
66
67impl SalarySlipLine {
68    /// Create a builder for SalarySlipLine
69    pub fn builder() -> SalarySlipLineBuilder {
70        <SalarySlipLineBuilder as Default>::default()
71    }
72
73    /// Create a new SalarySlipLine with required fields
74    pub fn new(salary_slip_id: Uuid, name: String, component_type: ComponentType, is_statutory: bool, amount: Decimal, gl_account_id: Uuid) -> Self {
75        Self {
76            id: Uuid::new_v4(),
77            salary_slip_id,
78            name,
79            component_type,
80            is_statutory,
81            amount,
82            gl_account_id,
83            source_kind: None,
84            source_ref: None,
85            metadata: AuditMetadata::default(),
86        }
87    }
88
89    /// Get the entity's unique identifier
90    pub fn id(&self) -> &Uuid {
91        &self.id
92    }
93
94    /// Get a strongly-typed ID for this entity
95    pub fn typed_id(&self) -> SalarySlipLineId {
96        SalarySlipLineId(self.id)
97    }
98
99    /// Get when this entity was created
100    pub fn created_at(&self) -> Option<&DateTime<Utc>> {
101        self.metadata.created_at.as_ref()
102    }
103
104    /// Get when this entity was last updated
105    pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
106        self.metadata.updated_at.as_ref()
107    }
108
109    /// Check if this entity is soft deleted
110    pub fn is_deleted(&self) -> bool {
111        self.metadata.deleted_at.is_some()
112    }
113
114    /// Check if this entity is active (not deleted)
115    pub fn is_active(&self) -> bool {
116        self.metadata.deleted_at.is_none()
117    }
118
119    /// Get when this entity was deleted
120    pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
121        self.metadata.deleted_at.as_ref()
122    }
123
124    /// Get who created this entity
125    pub fn created_by(&self) -> Option<&Uuid> {
126        self.metadata.created_by.as_ref()
127    }
128
129    /// Get who last updated this entity
130    pub fn updated_by(&self) -> Option<&Uuid> {
131        self.metadata.updated_by.as_ref()
132    }
133
134    /// Get who deleted this entity
135    pub fn deleted_by(&self) -> Option<&Uuid> {
136        self.metadata.deleted_by.as_ref()
137    }
138
139
140    // ==========================================================
141    // Fluent Setters (with_* for optional fields)
142    // ==========================================================
143
144    /// Set the source_kind field (chainable)
145    pub fn with_source_kind(mut self, value: String) -> Self {
146        self.source_kind = Some(value);
147        self
148    }
149
150    /// Set the source_ref field (chainable)
151    pub fn with_source_ref(mut self, value: Uuid) -> Self {
152        self.source_ref = Some(value);
153        self
154    }
155
156    // ==========================================================
157    // Partial Update
158    // ==========================================================
159
160    /// Apply partial updates from a map of field name to JSON value
161    pub fn apply_patch(&mut self, fields: std::collections::HashMap<String, serde_json::Value>) {
162        for (key, value) in fields {
163            match key.as_str() {
164                "salary_slip_id" => {
165                    if let Ok(v) = serde_json::from_value(value) { self.salary_slip_id = v; }
166                }
167                "name" => {
168                    if let Ok(v) = serde_json::from_value(value) { self.name = v; }
169                }
170                "component_type" => {
171                    if let Ok(v) = serde_json::from_value(value) { self.component_type = v; }
172                }
173                "is_statutory" => {
174                    if let Ok(v) = serde_json::from_value(value) { self.is_statutory = v; }
175                }
176                "amount" => {
177                    if let Ok(v) = serde_json::from_value(value) { self.amount = v; }
178                }
179                "gl_account_id" => {
180                    if let Ok(v) = serde_json::from_value(value) { self.gl_account_id = v; }
181                }
182                "source_kind" => {
183                    if let Ok(v) = serde_json::from_value(value) { self.source_kind = v; }
184                }
185                "source_ref" => {
186                    if let Ok(v) = serde_json::from_value(value) { self.source_ref = v; }
187                }
188                _ => {} // ignore unknown fields
189            }
190        }
191    }
192
193    // <<< CUSTOM METHODS START >>>
194    // <<< CUSTOM METHODS END >>>
195}
196
197impl super::Entity for SalarySlipLine {
198    type Id = Uuid;
199
200    fn entity_id(&self) -> &Self::Id {
201        &self.id
202    }
203
204    fn entity_type() -> &'static str {
205        "SalarySlipLine"
206    }
207}
208
209impl backbone_core::PersistentEntity for SalarySlipLine {
210    fn entity_id(&self) -> String {
211        self.id.to_string()
212    }
213    fn set_entity_id(&mut self, id: String) {
214        if let Ok(uuid) = uuid::Uuid::parse_str(&id) {
215            self.id = uuid;
216        }
217    }
218    fn created_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
219        self.metadata.created_at
220    }
221    fn set_created_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
222        self.metadata.created_at = Some(ts);
223    }
224    fn updated_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
225        self.metadata.updated_at
226    }
227    fn set_updated_at(&mut self, ts: chrono::DateTime<chrono::Utc>) {
228        self.metadata.updated_at = Some(ts);
229    }
230    fn deleted_at(&self) -> Option<chrono::DateTime<chrono::Utc>> {
231        self.metadata.deleted_at
232    }
233    fn set_deleted_at(&mut self, ts: Option<chrono::DateTime<chrono::Utc>>) {
234        self.metadata.deleted_at = ts;
235    }
236}
237
238impl backbone_orm::EntityRepoMeta for SalarySlipLine {
239    fn column_types() -> std::collections::HashMap<String, String> {
240        let mut m = std::collections::HashMap::new();
241        m.insert("id".to_string(), "uuid".to_string());
242        m.insert("salary_slip_id".to_string(), "uuid".to_string());
243        m.insert("gl_account_id".to_string(), "uuid".to_string());
244        m.insert("component_type".to_string(), "component_type".to_string());
245        m
246    }
247    fn search_fields() -> &'static [&'static str] {
248        &["name"]
249    }
250}
251
252/// Builder for SalarySlipLine entity
253///
254/// Provides a fluent API for constructing SalarySlipLine instances.
255/// System fields (id, metadata, timestamps) are auto-initialized.
256#[derive(Debug, Clone, Default)]
257pub struct SalarySlipLineBuilder {
258    salary_slip_id: Option<Uuid>,
259    name: Option<String>,
260    component_type: Option<ComponentType>,
261    is_statutory: Option<bool>,
262    amount: Option<Decimal>,
263    gl_account_id: Option<Uuid>,
264    source_kind: Option<String>,
265    source_ref: Option<Uuid>,
266}
267
268impl SalarySlipLineBuilder {
269    /// Set the salary_slip_id field (required)
270    pub fn salary_slip_id(mut self, value: Uuid) -> Self {
271        self.salary_slip_id = Some(value);
272        self
273    }
274
275    /// Set the name field (required)
276    pub fn name(mut self, value: String) -> Self {
277        self.name = Some(value);
278        self
279    }
280
281    /// Set the component_type field (default: `ComponentType::default()`)
282    pub fn component_type(mut self, value: ComponentType) -> Self {
283        self.component_type = Some(value);
284        self
285    }
286
287    /// Set the is_statutory field (default: `false`)
288    pub fn is_statutory(mut self, value: bool) -> Self {
289        self.is_statutory = Some(value);
290        self
291    }
292
293    /// Set the amount field (default: `Decimal::from(0)`)
294    pub fn amount(mut self, value: Decimal) -> Self {
295        self.amount = Some(value);
296        self
297    }
298
299    /// Set the gl_account_id field (required)
300    pub fn gl_account_id(mut self, value: Uuid) -> Self {
301        self.gl_account_id = Some(value);
302        self
303    }
304
305    /// Set the source_kind field (optional)
306    pub fn source_kind(mut self, value: String) -> Self {
307        self.source_kind = Some(value);
308        self
309    }
310
311    /// Set the source_ref field (optional)
312    pub fn source_ref(mut self, value: Uuid) -> Self {
313        self.source_ref = Some(value);
314        self
315    }
316
317    /// Build the SalarySlipLine entity
318    ///
319    /// Returns Err if any required field without a default is missing.
320    pub fn build(self) -> Result<SalarySlipLine, String> {
321        let salary_slip_id = self.salary_slip_id.ok_or_else(|| "salary_slip_id is required".to_string())?;
322        let name = self.name.ok_or_else(|| "name is required".to_string())?;
323        let gl_account_id = self.gl_account_id.ok_or_else(|| "gl_account_id is required".to_string())?;
324
325        Ok(SalarySlipLine {
326            id: Uuid::new_v4(),
327            salary_slip_id,
328            name,
329            component_type: self.component_type.unwrap_or_default(),
330            is_statutory: self.is_statutory.unwrap_or(false),
331            amount: self.amount.unwrap_or(Decimal::from(0)),
332            gl_account_id,
333            source_kind: self.source_kind,
334            source_ref: self.source_ref,
335            metadata: AuditMetadata::default(),
336        })
337    }
338}