1
use serde::{Deserialize, Deserializer};
2
use server::command::{FilterEntity, ReportFilter};
3
use sqlx::types::Uuid;
4

            
5
pub mod balance;
6
pub mod income_expense;
7
pub mod trial_balance;
8

            
9
pub fn empty_string_as_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
10
where
11
    D: Deserializer<'de>,
12
{
13
    let opt = Option::<String>::deserialize(deserializer)?;
14
    Ok(opt.filter(|s| !s.is_empty()))
15
}
16

            
17
#[derive(Deserialize)]
18
#[serde(tag = "type")]
19
enum FilterItem {
20
    #[serde(rename = "tag")]
21
    Tag {
22
        entities: Vec<String>,
23
        name: String,
24
        values: Vec<String>,
25
    },
26
    #[serde(rename = "account")]
27
    Account {
28
        account_id: String,
29
        #[serde(default, rename = "display_name")]
30
        _display_name: String,
31
        include_subtree: bool,
32
    },
33
    #[serde(rename = "group")]
34
    Group {
35
        logic: String,
36
        items: Vec<FilterItem>,
37
    },
38
}
39

            
40
#[derive(Deserialize)]
41
struct FilterGroup {
42
    logic: String,
43
    items: Vec<FilterItem>,
44
}
45

            
46
fn build_tag_entity_filter(entity: FilterEntity, name: &str, values: &[String]) -> ReportFilter {
47
    match values.len() {
48
        1 => ReportFilter::Tag {
49
            entity,
50
            name: name.to_string(),
51
            value: values[0].clone(),
52
        },
53
        _ => ReportFilter::TagIn {
54
            entity,
55
            name: name.to_string(),
56
            values: values.to_vec(),
57
        },
58
    }
59
}
60

            
61
fn build_filter_item(item: &FilterItem) -> Option<ReportFilter> {
62
    match item {
63
        FilterItem::Tag {
64
            entities,
65
            name,
66
            values,
67
        } => {
68
            if name.trim().is_empty() || values.is_empty() {
69
                return None;
70
            }
71
            let filters: Vec<ReportFilter> = entities
72
                .iter()
73
                .filter_map(|e| {
74
                    let entity = match e.as_str() {
75
                        "account" => FilterEntity::Account,
76
                        "transaction" => FilterEntity::Transaction,
77
                        "split" => FilterEntity::Split,
78
                        _ => return None,
79
                    };
80
                    Some(build_tag_entity_filter(entity, name, values))
81
                })
82
                .collect();
83
            match filters.len() {
84
                0 => None,
85
                1 => filters.into_iter().next(),
86
                _ => Some(ReportFilter::Or(filters)),
87
            }
88
        }
89
        FilterItem::Account {
90
            account_id,
91
            include_subtree,
92
            ..
93
        } => {
94
            let id = account_id.parse::<Uuid>().ok()?;
95
            if *include_subtree {
96
                Some(ReportFilter::AccountSubtree(id))
97
            } else {
98
                Some(ReportFilter::AccountEq(id))
99
            }
100
        }
101
        FilterItem::Group { logic, items } => {
102
            let filters: Vec<ReportFilter> = items.iter().filter_map(build_filter_item).collect();
103
            match filters.len() {
104
                0 => None,
105
                1 => filters.into_iter().next(),
106
                _ => {
107
                    if logic == "or" {
108
                        Some(ReportFilter::Or(filters))
109
                    } else {
110
                        Some(ReportFilter::And(filters))
111
                    }
112
                }
113
            }
114
        }
115
    }
116
}
117

            
118
fn build_filter_from_group(group: &FilterGroup) -> Option<ReportFilter> {
119
    let filters: Vec<ReportFilter> = group.items.iter().filter_map(build_filter_item).collect();
120
    match filters.len() {
121
        0 => None,
122
        1 => filters.into_iter().next(),
123
        _ => {
124
            if group.logic == "or" {
125
                Some(ReportFilter::Or(filters))
126
            } else {
127
                Some(ReportFilter::And(filters))
128
            }
129
        }
130
    }
131
}
132

            
133
#[must_use]
134
pub fn build_report_filter(
135
    tag_filters: Option<&str>,
136
    tag_filter_mode: Option<&str>,
137
) -> Option<ReportFilter> {
138
    let raw = tag_filters.filter(|s| !s.is_empty())?;
139

            
140
    if tag_filter_mode == Some("script") {
141
        return build_filter_from_sexpr(raw);
142
    }
143

            
144
    let group: FilterGroup = serde_json::from_str(raw).ok()?;
145
    build_filter_from_group(&group)
146
}
147

            
148
#[cfg(feature = "scripting")]
149
fn build_filter_from_sexpr(raw: &str) -> Option<ReportFilter> {
150
    ReportFilter::from_sexpr(raw).ok()
151
}
152

            
153
#[cfg(not(feature = "scripting"))]
154
fn build_filter_from_sexpr(_raw: &str) -> Option<ReportFilter> {
155
    None
156
}