Skip to main content

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