1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[serde(rename_all = "lowercase")]
11pub enum PlanStatus {
12 Draft,
13 Active,
14 Completed,
15}
16
17impl PlanStatus {
18 #[must_use]
20 pub const fn as_str(&self) -> &'static str {
21 match self {
22 Self::Draft => "draft",
23 Self::Active => "active",
24 Self::Completed => "completed",
25 }
26 }
27
28 #[must_use]
30 pub fn from_str(s: &str) -> Self {
31 match s.to_lowercase().as_str() {
32 "active" => Self::Active,
33 "completed" => Self::Completed,
34 _ => Self::Draft,
35 }
36 }
37}
38
39impl Default for PlanStatus {
40 fn default() -> Self {
41 Self::Draft
42 }
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
53pub struct Plan {
54 pub id: String,
56
57 pub short_id: Option<String>,
59
60 pub project_id: String,
62
63 pub project_path: String,
65
66 pub title: String,
68
69 pub content: Option<String>,
71
72 pub status: PlanStatus,
74
75 pub success_criteria: Option<String>,
77
78 pub created_in_session: Option<String>,
80
81 pub completed_in_session: Option<String>,
83
84 pub created_at: i64,
86
87 pub updated_at: i64,
89
90 pub completed_at: Option<i64>,
92}
93
94impl Plan {
95 pub fn new(project_id: String, project_path: String, title: String) -> Self {
97 let now = chrono::Utc::now().timestamp_millis();
98 let id = format!("plan_{}", &uuid::Uuid::new_v4().to_string()[..12]);
99
100 Self {
101 id,
102 short_id: None,
103 project_id,
104 project_path,
105 title,
106 content: None,
107 status: PlanStatus::Draft,
108 success_criteria: None,
109 created_in_session: None,
110 completed_in_session: None,
111 created_at: now,
112 updated_at: now,
113 completed_at: None,
114 }
115 }
116
117 #[must_use]
119 pub fn with_content(mut self, content: &str) -> Self {
120 self.content = Some(content.to_string());
121 self
122 }
123
124 #[must_use]
126 pub fn with_status(mut self, status: PlanStatus) -> Self {
127 self.status = status;
128 self
129 }
130
131 #[must_use]
133 pub fn with_success_criteria(mut self, criteria: &str) -> Self {
134 self.success_criteria = Some(criteria.to_string());
135 self
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 #[test]
144 fn test_new_plan() {
145 let plan = Plan::new(
146 "proj_123".to_string(),
147 "/home/user/myproject".to_string(),
148 "Authentication System".to_string(),
149 );
150
151 assert!(plan.id.starts_with("plan_"));
152 assert_eq!(plan.project_id, "proj_123");
153 assert_eq!(plan.title, "Authentication System");
154 assert_eq!(plan.status, PlanStatus::Draft);
155 }
156
157 #[test]
158 fn test_plan_status_parsing() {
159 assert_eq!(PlanStatus::from_str("draft"), PlanStatus::Draft);
160 assert_eq!(PlanStatus::from_str("active"), PlanStatus::Active);
161 assert_eq!(PlanStatus::from_str("completed"), PlanStatus::Completed);
162 assert_eq!(PlanStatus::from_str("unknown"), PlanStatus::Draft);
163 }
164}