1
use chrono::{DateTime, Utc};
2
use finance::error::{FinanceError, ReportError};
3
use sqlx::types::Uuid;
4
use supp_macro::command;
5

            
6
use crate::{config::ConfigError, user::User};
7

            
8
use super::{CmdError, CmdResult, PeriodData, ReportData, ReportFilter, ReportMeta};
9

            
10
mod fetch;
11
mod filter;
12
mod period;
13
mod tree;
14

            
15
#[cfg(test)]
16
mod command_tests;
17
#[cfg(test)]
18
mod unit_tests;
19

            
20
use fetch::{
21
    fetch_accounts, fetch_balance_splits_filtered_no_conversion,
22
    fetch_balance_splits_filtered_with_conversion, fetch_balance_splits_no_conversion,
23
    fetch_balance_splits_with_conversion, fetch_date_range_splits_filtered_no_conversion,
24
    fetch_date_range_splits_filtered_with_conversion, fetch_date_range_splits_no_conversion,
25
    fetch_date_range_splits_with_conversion, fetch_target_symbol,
26
};
27
use period::{generate_month_boundaries, generate_quarter_boundaries, generate_year_boundaries};
28
use tree::build_tree;
29

            
30
command! {
31
    BalanceReport {
32
        #[required]
33
        user_id: Uuid,
34
        #[optional]
35
        target_commodity_id: Uuid,
36
        #[optional]
37
        as_of: DateTime<Utc>,
38
        #[optional]
39
        report_filter: ReportFilter,
40
    } => {
41
        let user = User { id: user_id };
42
        let mut conn = user.get_connection().await.map_err(|err| {
43
            log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
44
            ConfigError::DB
45
        })?;
46

            
47
        let accounts = fetch_accounts(&mut conn).await?;
48

            
49
        let amounts = match (&target_commodity_id, &report_filter) {
50
            (Some(tid), Some(f)) => {
51
                let sym = fetch_target_symbol(&mut conn, *tid).await?;
52
                fetch_balance_splits_filtered_with_conversion(&mut conn, *tid, &sym, as_of, f).await?
53
            }
54
            (Some(tid), None) => {
55
                let sym = fetch_target_symbol(&mut conn, *tid).await?;
56
                fetch_balance_splits_with_conversion(&mut conn, *tid, &sym, as_of).await?
57
            }
58
            (None, Some(f)) => {
59
                fetch_balance_splits_filtered_no_conversion(&mut conn, as_of, f).await?
60
            }
61
            (None, None) => {
62
                fetch_balance_splits_no_conversion(&mut conn, as_of).await?
63
            }
64
        };
65

            
66
        let roots = build_tree(&accounts, &amounts);
67
        let period = PeriodData { label: None, roots };
68

            
69
        Ok(Some(CmdResult::Report(ReportData {
70
            meta: ReportMeta {
71
                date_from: None,
72
                date_to: as_of,
73
                target_commodity_id,
74
            },
75
            periods: vec![period],
76
        })))
77
    }
78
25
}
79

            
80
command! {
81
    IncomeExpenseReport {
82
        #[required]
83
        user_id: Uuid,
84
        #[required]
85
        date_from: DateTime<Utc>,
86
        #[required]
87
        date_to: DateTime<Utc>,
88
        #[optional]
89
        target_commodity_id: Uuid,
90
        #[optional]
91
        period_grouping: String,
92
        #[optional]
93
        report_filter: ReportFilter,
94
    } => {
95
        let user = User { id: user_id };
96
        let mut conn = user.get_connection().await.map_err(|err| {
97
            log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
98
            ConfigError::DB
99
        })?;
100

            
101
        let accounts = fetch_accounts(&mut conn).await?;
102

            
103
        let period_boundaries = match &period_grouping {
104
            Some(g) => match g.as_str() {
105
                "month" => generate_month_boundaries(date_from, date_to),
106
                "quarter" => generate_quarter_boundaries(date_from, date_to),
107
                "year" => generate_year_boundaries(date_from, date_to),
108
                other => return Err(CmdError::Finance(FinanceError::Report(
109
                    ReportError::InvalidPeriodGrouping(other.to_string()),
110
                ))),
111
            },
112
            None => vec![],
113
        };
114

            
115
        let periods = if period_boundaries.is_empty() {
116
            let amounts = match (&target_commodity_id, &report_filter) {
117
                (Some(tid), Some(f)) => {
118
                    let sym = fetch_target_symbol(&mut conn, *tid).await?;
119
                    fetch_date_range_splits_filtered_with_conversion(&mut conn, *tid, &sym, date_from, date_to, f).await?
120
                }
121
                (Some(tid), None) => {
122
                    let sym = fetch_target_symbol(&mut conn, *tid).await?;
123
                    fetch_date_range_splits_with_conversion(&mut conn, *tid, &sym, date_from, date_to).await?
124
                }
125
                (None, Some(f)) => {
126
                    fetch_date_range_splits_filtered_no_conversion(&mut conn, date_from, date_to, f).await?
127
                }
128
                (None, None) => {
129
                    fetch_date_range_splits_no_conversion(&mut conn, date_from, date_to).await?
130
                }
131
            };
132
            let roots = build_tree(&accounts, &amounts);
133
            vec![PeriodData { label: None, roots }]
134
        } else {
135
            let mut periods = Vec::new();
136
            for (label, pfrom, pto) in &period_boundaries {
137
                let amounts = match (&target_commodity_id, &report_filter) {
138
                    (Some(tid), Some(f)) => {
139
                        let sym = fetch_target_symbol(&mut conn, *tid).await?;
140
                        fetch_date_range_splits_filtered_with_conversion(&mut conn, *tid, &sym, *pfrom, *pto, f).await?
141
                    }
142
                    (Some(tid), None) => {
143
                        let sym = fetch_target_symbol(&mut conn, *tid).await?;
144
                        fetch_date_range_splits_with_conversion(&mut conn, *tid, &sym, *pfrom, *pto).await?
145
                    }
146
                    (None, Some(f)) => {
147
                        fetch_date_range_splits_filtered_no_conversion(&mut conn, *pfrom, *pto, f).await?
148
                    }
149
                    (None, None) => {
150
                        fetch_date_range_splits_no_conversion(&mut conn, *pfrom, *pto).await?
151
                    }
152
                };
153
                let roots = build_tree(&accounts, &amounts);
154
                periods.push(PeriodData {
155
                    label: Some(label.clone()),
156
                    roots,
157
                });
158
            }
159
            periods
160
        };
161

            
162
        Ok(Some(CmdResult::Report(ReportData {
163
            meta: ReportMeta {
164
                date_from: Some(date_from),
165
                date_to: Some(date_to),
166
                target_commodity_id,
167
            },
168
            periods,
169
        })))
170
    }
171
23
}
172

            
173
command! {
174
    TrialBalance {
175
        #[required]
176
        user_id: Uuid,
177
        #[required]
178
        date_from: DateTime<Utc>,
179
        #[required]
180
        date_to: DateTime<Utc>,
181
        #[optional]
182
        target_commodity_id: Uuid,
183
        #[optional]
184
        report_filter: ReportFilter,
185
    } => {
186
        let user = User { id: user_id };
187
        let mut conn = user.get_connection().await.map_err(|err| {
188
            log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
189
            ConfigError::DB
190
        })?;
191

            
192
        let accounts = fetch_accounts(&mut conn).await?;
193

            
194
        let amounts = match (&target_commodity_id, &report_filter) {
195
            (Some(tid), Some(f)) => {
196
                let sym = fetch_target_symbol(&mut conn, *tid).await?;
197
                fetch_date_range_splits_filtered_with_conversion(&mut conn, *tid, &sym, date_from, date_to, f).await?
198
            }
199
            (Some(tid), None) => {
200
                let sym = fetch_target_symbol(&mut conn, *tid).await?;
201
                fetch_date_range_splits_with_conversion(&mut conn, *tid, &sym, date_from, date_to).await?
202
            }
203
            (None, Some(f)) => {
204
                fetch_date_range_splits_filtered_no_conversion(&mut conn, date_from, date_to, f).await?
205
            }
206
            (None, None) => {
207
                fetch_date_range_splits_no_conversion(&mut conn, date_from, date_to).await?
208
            }
209
        };
210

            
211
        let roots = build_tree(&accounts, &amounts);
212
        let period = PeriodData { label: None, roots };
213

            
214
        Ok(Some(CmdResult::Report(ReportData {
215
            meta: ReportMeta {
216
                date_from: Some(date_from),
217
                date_to: Some(date_to),
218
                target_commodity_id,
219
            },
220
            periods: vec![period],
221
        })))
222
    }
223
17
}