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}
106
107impl NewEntry {
108    pub fn builder() -> NewEntryBuilder {
109        NewEntryBuilder::default()
110    }
111
112    pub(super) fn data_source(&self) -> DataSource {
113        DataSource::Local
114    }
115}
116
117impl IntoEvents<EntryEvent> for NewEntry {
118    fn into_events(self) -> EntityEvents<EntryEvent> {
119        EntityEvents::init(
120            self.id,
121            [EntryEvent::Initialized {
122                values: EntryValues {
123                    id: self.id,
124                    version: 1,
125                    transaction_id: self.transaction_id,
126                    journal_id: self.journal_id,
127                    account_id: self.account_id,
128                    entry_type: self.entry_type,
129                    sequence: self.sequence,
130                    layer: self.layer,
131                    units: self.units,
132                    currency: self.currency,
133                    direction: self.direction,
134                    description: self.description,
135                },
136            }],
137        )
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn it_builds() {
147        let mut builder = NewEntry::builder();
148        let currency = "USD".parse::<Currency>().unwrap();
149        let entry_id = EntryId::new();
150        builder
151            .id(entry_id)
152            .transaction_id(TransactionId::new())
153            .account_id(AccountId::new())
154            .journal_id(JournalId::new())
155            .layer(Layer::Settled)
156            .entry_type("ENTRY_TYPE")
157            .sequence(1u32)
158            .units(rust_decimal::Decimal::from(1))
159            .currency(currency)
160            .direction(DebitOrCredit::Debit);
161        let new_entry = builder.build().unwrap();
162        assert_eq!(new_entry.id, entry_id);
163    }
164
165    #[test]
166    fn fails_when_missing_required_fields() {
167        let mut builder = NewEntry::builder();
168        builder.id(EntryId::new());
169        let new_entry = builder.build();
170        assert!(new_entry.is_err());
171    }
172}