Skip to main content

backbone_banking/domain/entity/
reconcile_preset.rs

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