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