cala_ledger/entry/
entity.rs

1use derive_builder::Builder;
2use es_entity::*;
3use serde::{Deserialize, Serialize};
4
5use crate::primitives::*;
6pub use cala_types::{entry::*, primitives::EntryId};
7
8#[derive(EsEvent, Debug, Serialize, Deserialize)]
9#[serde(tag = "type", rename_all = "snake_case")]
10#[es_event(id = "EntryId")]
11pub enum EntryEvent {
12    #[cfg(feature = "import")]
13    Imported {
14        source: DataSource,
15        values: EntryValues,
16    },
17    Initialized {
18        values: EntryValues,
19    },
20}
21
22#[derive(EsEntity, Builder)]
23#[builder(pattern = "owned", build_fn(error = "EsEntityError"))]
24pub struct Entry {
25    pub id: EntryId,
26    values: EntryValues,
27    pub(super) events: EntityEvents<EntryEvent>,
28}
29
30impl Entry {
31    #[cfg(feature = "import")]
32    pub(super) fn import(source: DataSourceId, values: EntryValues) -> Self {
33        let events = EntityEvents::init(
34            values.id,
35            [EntryEvent::Imported {
36                source: DataSource::Remote { id: source },
37                values,
38            }],
39        );
40        Self::try_from_events(events).expect("Failed to build entry from events")
41    }
42
43    pub fn id(&self) -> EntryId {
44        self.values.id
45    }
46
47    pub fn values(&self) -> &EntryValues {
48        &self.values
49    }
50
51    pub fn into_values(self) -> EntryValues {
52        self.values
53    }
54
55    pub fn created_at(&self) -> chrono::DateTime<chrono::Utc> {
56        self.events
57            .entity_first_persisted_at()
58            .expect("Entity not persisted")
59    }
60}
61
62impl TryFromEvents<EntryEvent> for Entry {
63    fn try_from_events(events: EntityEvents<EntryEvent>) -> Result<Self, EsEntityError> {
64        let mut builder = EntryBuilder::default();
65        for event in events.iter_all() {
66            match event {
67                #[cfg(feature = "import")]
68                EntryEvent::Imported { source: _, values } => {
69                    builder = builder.id(values.id).values(values.clone());
70                }
71                EntryEvent::Initialized { values } => {
72                    builder = builder.id(values.id).values(values.clone());
73                }
74            }
75        }
76        builder.events(events).build()
77    }
78}
79
80#[derive(Builder, Debug)]
81#[allow(dead_code)]
82pub struct NewEntry {
83    #[builder(setter(into))]
84    pub id: EntryId,
85    #[builder(setter(into))]
86    pub(super) transaction_id: TransactionId,
87    #[builder(setter(into))]
88    pub(super) journal_id: JournalId,
89    #[builder(setter(into))]
90    pub(super) account_id: AccountId,
91    #[builder(setter(into))]
92    pub(super) entry_type: String,
93    #[builder(setter(into))]
94    pub(super) sequence: u32,
95    #[builder(default)]
96    pub(super) layer: Layer,
97    #[builder(setter(into))]
98    pub(super) units: rust_decimal::Decimal,
99    #[builder(setter(into))]
100    pub(super) currency: Currency,
101    #[builder(default)]
102    pub(super) direction: DebitOrCredit,
103    #[builder(setter(strip_option), default)]
104    pub(super) description: Option<String>,
105    #[builder(setter(into), default)]
106    pub(super) metadata: Option<serde_json::Value>,
107}
108
109impl NewEntry {
110    pub fn builder() -> NewEntryBuilder {
111        NewEntryBuilder::default()
112    }
113
114    pub(super) fn data_source(&self) -> DataSource {
115        DataSource::Local
116    }
117}
118
119impl IntoEvents<EntryEvent> for NewEntry {
120    fn into_events(self) -> EntityEvents<EntryEvent> {
121        EntityEvents::init(
122            self.id,
123            [EntryEvent::Initialized {
124                values: EntryValues {
125                    id: self.id,
126                    version: 1,
127                    transaction_id: self.transaction_id,
128                    journal_id: self.journal_id,
129                    account_id: self.account_id,
130                    entry_type: self.entry_type,
131                    sequence: self.sequence,
132                    layer: self.layer,
133                    units: self.units,
134                    currency: self.currency,
135                    direction: self.direction,
136                    description: self.description,
137                    metadata: self.metadata,
138                },
139            }],
140        )
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use super::*;
147
148    #[test]
149    fn it_builds() {
150        let mut builder = NewEntry::builder();
151        let currency = "USD".parse::<Currency>().unwrap();
152        let entry_id = EntryId::new();
153        builder
154            .id(entry_id)
155            .transaction_id(TransactionId::new())
156            .account_id(AccountId::new())
157            .journal_id(JournalId::new())
158            .layer(Layer::Settled)
159            .entry_type("ENTRY_TYPE")
160            .sequence(1u32)
161            .units(rust_decimal::Decimal::from(1))
162            .currency(currency)
163            .direction(DebitOrCredit::Debit)
164            .metadata(Some(serde_json::Value::String(String::new())));
165        let new_entry = builder.build().unwrap();
166        assert_eq!(new_entry.id, entry_id);
167    }
168
169    #[test]
170    fn fails_when_missing_required_fields() {
171        let mut builder = NewEntry::builder();
172        builder.id(EntryId::new());
173        let new_entry = builder.build();
174        assert!(new_entry.is_err());
175    }
176}