backbone_banking/domain/entity/
reconcile_preset.rs1use 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#[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 pub fn builder() -> ReconcilePresetBuilder {
70 <ReconcilePresetBuilder as Default>::default()
71 }
72
73 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 pub fn id(&self) -> &Uuid {
90 &self.id
91 }
92
93 pub fn typed_id(&self) -> ReconcilePresetId {
95 ReconcilePresetId(self.id)
96 }
97
98 pub fn created_at(&self) -> Option<&DateTime<Utc>> {
100 self.metadata.created_at.as_ref()
101 }
102
103 pub fn updated_at(&self) -> Option<&DateTime<Utc>> {
105 self.metadata.updated_at.as_ref()
106 }
107
108 pub fn is_deleted(&self) -> bool {
110 self.metadata.deleted_at.is_some()
111 }
112
113 pub fn is_active(&self) -> bool {
115 self.metadata.deleted_at.is_none()
116 }
117
118 pub fn deleted_at(&self) -> Option<&DateTime<Utc>> {
120 self.metadata.deleted_at.as_ref()
121 }
122
123 pub fn created_by(&self) -> Option<&Uuid> {
125 self.metadata.created_by.as_ref()
126 }
127
128 pub fn updated_by(&self) -> Option<&Uuid> {
130 self.metadata.updated_by.as_ref()
131 }
132
133 pub fn deleted_by(&self) -> Option<&Uuid> {
135 self.metadata.deleted_by.as_ref()
136 }
137
138 pub fn status(&self) -> &PresetStatus {
140 &self.status
141 }
142
143
144 pub fn with_note(mut self, value: String) -> Self {
150 self.note = Some(value);
151 self
152 }
153
154 pub fn with_tolerance_percent(mut self, value: Decimal) -> Self {
156 self.tolerance_percent = Some(value);
157 self
158 }
159
160 pub fn with_days_window(mut self, value: i32) -> Self {
162 self.days_window = Some(value);
163 self
164 }
165
166 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 _ => {} }
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#[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 pub fn name(mut self, value: String) -> Self {
274 self.name = Some(value);
275 self
276 }
277
278 pub fn note(mut self, value: String) -> Self {
280 self.note = Some(value);
281 self
282 }
283
284 pub fn priority(mut self, value: i32) -> Self {
286 self.priority = Some(value);
287 self
288 }
289
290 pub fn match_on(mut self, value: MatchOn) -> Self {
292 self.match_on = Some(value);
293 self
294 }
295
296 pub fn tolerance_percent(mut self, value: Decimal) -> Self {
298 self.tolerance_percent = Some(value);
299 self
300 }
301
302 pub fn days_window(mut self, value: i32) -> Self {
304 self.days_window = Some(value);
305 self
306 }
307
308 pub fn status(mut self, value: PresetStatus) -> Self {
310 self.status = Some(value);
311 self
312 }
313
314 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}