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}
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}