cala_ledger/entry/
entity.rs1use 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}