1use clap::Subcommand;
6
7use crate::display::category::{
8 format_category_details, format_category_tree, format_group_details, format_group_list,
9};
10use crate::error::{EnvelopeError, EnvelopeResult};
11use crate::services::CategoryService;
12use crate::storage::Storage;
13
14#[derive(Subcommand)]
16pub enum CategoryCommands {
17 List,
19
20 Create {
22 name: String,
24 #[arg(short, long)]
26 group: String,
27 #[arg(long)]
29 goal: Option<String>,
30 },
31
32 Show {
34 category: String,
36 },
37
38 Edit {
40 category: String,
42 #[arg(short, long)]
44 name: Option<String>,
45 #[arg(long)]
47 goal: Option<String>,
48 #[arg(long)]
50 clear_goal: bool,
51 },
52
53 Move {
55 category: String,
57 #[arg(short, long)]
59 to: String,
60 },
61
62 Delete {
64 category: String,
66 },
67
68 #[command(name = "create-group")]
70 CreateGroup {
71 name: String,
73 },
74
75 #[command(name = "list-groups")]
77 ListGroups,
78
79 #[command(name = "show-group")]
81 ShowGroup {
82 group: String,
84 },
85
86 #[command(name = "edit-group")]
88 EditGroup {
89 group: String,
91 #[arg(short, long)]
93 name: Option<String>,
94 },
95
96 #[command(name = "delete-group")]
98 DeleteGroup {
99 group: String,
101 #[arg(long)]
103 force: bool,
104 },
105}
106
107pub fn handle_category_command(storage: &Storage, cmd: CategoryCommands) -> EnvelopeResult<()> {
109 let service = CategoryService::new(storage);
110
111 match cmd {
112 CategoryCommands::List => {
113 let groups = service.list_groups_with_categories()?;
114 print!("{}", format_category_tree(&groups));
115 }
116
117 CategoryCommands::Create { name, group, goal } => {
118 let group = service
119 .find_group(&group)?
120 .ok_or_else(|| EnvelopeError::NotFound {
121 entity_type: "Category Group",
122 identifier: group.clone(),
123 })?;
124
125 let category = service.create_category(&name, group.id)?;
126
127 if let Some(goal_str) = goal {
129 let goal_money = crate::models::Money::parse(&goal_str).map_err(|e| {
130 EnvelopeError::Validation(format!("Invalid goal amount: {}", e))
131 })?;
132 service.update_category(category.id, None, Some(goal_money.cents()), false)?;
133 }
134
135 println!("Created category: {}", category.name);
136 println!(" Group: {}", group.name);
137 println!(" ID: {}", category.id);
138 }
139
140 CategoryCommands::Show { category } => {
141 let cat = service
142 .find_category(&category)?
143 .ok_or_else(|| EnvelopeError::category_not_found(&category))?;
144
145 let group = service.get_group(cat.group_id)?;
146 print!("{}", format_category_details(&cat, group.as_ref()));
147 }
148
149 CategoryCommands::Edit {
150 category,
151 name,
152 goal,
153 clear_goal,
154 } => {
155 let cat = service
156 .find_category(&category)?
157 .ok_or_else(|| EnvelopeError::category_not_found(&category))?;
158
159 if name.is_none() && goal.is_none() && !clear_goal {
160 println!("No changes specified. Use --name, --goal, or --clear-goal.");
161 return Ok(());
162 }
163
164 let goal_cents = if let Some(goal_str) = goal {
165 let goal_money = crate::models::Money::parse(&goal_str).map_err(|e| {
166 EnvelopeError::Validation(format!("Invalid goal amount: {}", e))
167 })?;
168 Some(goal_money.cents())
169 } else {
170 None
171 };
172
173 let updated =
174 service.update_category(cat.id, name.as_deref(), goal_cents, clear_goal)?;
175 println!("Updated category: {}", updated.name);
176 }
177
178 CategoryCommands::Move { category, to } => {
179 let cat = service
180 .find_category(&category)?
181 .ok_or_else(|| EnvelopeError::category_not_found(&category))?;
182
183 let target_group = service
184 .find_group(&to)?
185 .ok_or_else(|| EnvelopeError::NotFound {
186 entity_type: "Category Group",
187 identifier: to.clone(),
188 })?;
189
190 let moved = service.move_category(cat.id, target_group.id)?;
191 println!("Moved '{}' to group '{}'", moved.name, target_group.name);
192 }
193
194 CategoryCommands::Delete { category } => {
195 let cat = service
196 .find_category(&category)?
197 .ok_or_else(|| EnvelopeError::category_not_found(&category))?;
198
199 service.delete_category(cat.id)?;
200 println!("Deleted category: {}", cat.name);
201 }
202
203 CategoryCommands::CreateGroup { name } => {
204 let group = service.create_group(&name)?;
205 println!("Created category group: {}", group.name);
206 println!(" ID: {}", group.id);
207 }
208
209 CategoryCommands::ListGroups => {
210 let groups = service.list_groups()?;
211 print!("{}", format_group_list(&groups));
212 }
213
214 CategoryCommands::ShowGroup { group } => {
215 let g = service
216 .find_group(&group)?
217 .ok_or_else(|| EnvelopeError::NotFound {
218 entity_type: "Category Group",
219 identifier: group.clone(),
220 })?;
221
222 let categories = service.list_categories_in_group(g.id)?;
223 print!("{}", format_group_details(&g, &categories));
224 }
225
226 CategoryCommands::EditGroup { group, name } => {
227 let g = service
228 .find_group(&group)?
229 .ok_or_else(|| EnvelopeError::NotFound {
230 entity_type: "Category Group",
231 identifier: group.clone(),
232 })?;
233
234 if name.is_none() {
235 println!("No changes specified. Use --name to change the group name.");
236 return Ok(());
237 }
238
239 let updated = service.update_group(g.id, name.as_deref())?;
240 println!("Updated category group: {}", updated.name);
241 }
242
243 CategoryCommands::DeleteGroup { group, force } => {
244 let g = service
245 .find_group(&group)?
246 .ok_or_else(|| EnvelopeError::NotFound {
247 entity_type: "Category Group",
248 identifier: group.clone(),
249 })?;
250
251 service.delete_group(g.id, force)?;
252 println!("Deleted category group: {}", g.name);
253 }
254 }
255
256 Ok(())
257}