tiller_sync/model/
category.rs1use crate::error::Res;
2use crate::model::items::{Item, Items};
3use crate::utils;
4use anyhow::bail;
5use clap::Parser;
6use schemars::JsonSchema;
7use serde::{Deserialize, Serialize};
8use std::collections::BTreeMap;
9
10pub type Categories = Items<Category>;
12
13#[derive(Default, Debug, Clone, Eq, PartialEq, Serialize, Deserialize, JsonSchema)]
15#[serde(rename_all = "snake_case")]
16pub struct Category {
17 pub(crate) category: String,
19
20 pub(crate) category_group: String,
24
25 #[serde(rename = "type")]
28 pub(crate) r#type: String,
29
30 pub(crate) hide_from_reports: String,
34
35 pub(crate) other_fields: BTreeMap<String, String>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub(crate) original_order: Option<u64>,
41}
42
43impl Category {
44 pub fn merge_updates(&mut self, update: CategoryUpdates) {
46 if let Some(x) = update.category {
47 self.category = x;
48 }
49 if let Some(x) = update.group {
50 self.category_group = x;
51 }
52 if let Some(x) = update.r#type {
53 self.r#type = x;
54 }
55 if let Some(x) = update.hide_from_reports {
56 self.hide_from_reports = x;
57 }
58
59 for (key, val) in update.other_fields {
60 self.other_fields.insert(key, val);
61 }
62 }
63}
64
65impl Item for Category {
66 fn set_with_header<S1, S2>(&mut self, header: S1, value: S2) -> Res<()>
67 where
68 S1: AsRef<str>,
69 S2: Into<String>,
70 {
71 let header = header.as_ref();
72 let value = value.into();
73
74 match CategoryColumn::from_header(header) {
75 Ok(col) => match col {
76 CategoryColumn::Category => self.category = value,
77 CategoryColumn::Group => self.category_group = value,
78 CategoryColumn::Type => self.r#type = value,
79 CategoryColumn::HideFromReports => self.hide_from_reports = value,
80 },
81 Err(_) => {
82 let _ = self.other_fields.insert(header.to_string(), value);
83 }
84 }
85
86 Ok(())
87 }
88
89 fn get_by_header(&self, header: &str) -> String {
90 match CategoryColumn::from_header(header) {
91 Ok(col) => match col {
92 CategoryColumn::Category => self.category.clone(),
93 CategoryColumn::Group => self.category_group.clone(),
94 CategoryColumn::Type => self.r#type.clone(),
95 CategoryColumn::HideFromReports => self.hide_from_reports.clone(),
96 },
97 Err(_) => self.other_fields.get(header).cloned().unwrap_or_default(),
98 }
99 }
100
101 fn set_original_order(&mut self, original_order: u64) {
102 self.original_order = Some(original_order)
103 }
104
105 fn get_original_order(&self) -> Option<u64> {
106 self.original_order
107 }
108}
109
110#[derive(Default, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Serialize, Deserialize)]
112#[serde(rename_all = "snake_case")]
113pub enum CategoryColumn {
114 #[default]
116 Category,
117 Group,
120 #[serde(rename = "type")]
123 Type,
124 HideFromReports,
126}
127
128serde_plain::derive_display_from_serialize!(CategoryColumn);
129serde_plain::derive_fromstr_from_deserialize!(CategoryColumn);
130
131impl CategoryColumn {
132 pub fn from_header(header: impl AsRef<str>) -> Res<CategoryColumn> {
133 let header_str = header.as_ref();
134 match header_str {
135 CATEGORY_STR => Ok(CategoryColumn::Category),
136 GROUP_STR => Ok(CategoryColumn::Group),
137 TYPE_STR => Ok(CategoryColumn::Type),
138 HIDE_FROM_REPORTS_STR => Ok(CategoryColumn::HideFromReports),
139 bad => bail!("Invalid category column name '{bad}'"),
140 }
141 }
142}
143
144pub(super) const CATEGORY_STR: &str = "Category";
145pub(super) const GROUP_STR: &str = "Group";
146pub(super) const TYPE_STR: &str = "Type";
147pub(super) const HIDE_FROM_REPORTS_STR: &str = "Hide From Reports";
148
149#[derive(Debug, Default, Clone, Parser, Serialize, Deserialize, JsonSchema)]
155pub struct CategoryUpdates {
156 #[serde(skip_serializing_if = "Option::is_none")]
160 #[arg(long)]
161 pub category: Option<String>,
162
163 #[serde(skip_serializing_if = "Option::is_none")]
167 #[arg(long)]
168 pub group: Option<String>,
169
170 #[serde(skip_serializing_if = "Option::is_none")]
173 #[arg(long, name = "type")]
174 pub r#type: Option<String>,
175
176 #[serde(skip_serializing_if = "Option::is_none")]
180 #[arg(long)]
181 pub hide_from_reports: Option<String>,
182
183 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
185 #[arg(long = "other-field", value_parser = utils::parse_key_val)]
186 pub other_fields: BTreeMap<String, String>,
187}