Skip to main content

backbone_pos/domain/entity/
pos_closing_entry.rs

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