1use serde::{Deserialize, Serialize};
2use ts_rs::TS;
3
4use crate::{ColumnRef, Metadata, TaskId};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
7#[ts(export)]
8pub struct Task {
9 pub id: TaskId,
10 pub title: String,
11 pub status: Status,
12 pub column: ColumnRef,
13 pub metadata: Metadata,
14 pub dependencies: Vec<TaskId>,
15 pub references: Vec<TaskId>,
16 pub created_at: chrono::DateTime<chrono::Utc>,
17 pub updated_at: chrono::DateTime<chrono::Utc>,
18}
19
20impl Task {
21 pub fn builder() -> TaskBuilder {
22 TaskBuilder::default()
23 }
24
25 pub fn is_done(&self) -> bool {
26 self.status == Status::Done
27 }
28
29 pub fn is_blocked(&self) -> bool {
30 self.status == Status::Blocked || !self.dependencies.is_empty()
31 }
32
33 pub fn set_status(&mut self, status: Status) {
34 self.status = status;
35 self.updated_at = chrono::Utc::now();
36 }
37
38 pub fn set_assignee(&mut self, assignee: impl Into<String>) {
39 self.metadata.assign = Some(assignee.into());
40 self.updated_at = chrono::Utc::now();
41 }
42}
43
44#[derive(Debug, Clone, Default)]
45pub struct TaskBuilder {
46 id: Option<TaskId>,
47 title: Option<String>,
48 status: Option<Status>,
49 column: Option<ColumnRef>,
50 metadata: Option<Metadata>,
51 dependencies: Vec<TaskId>,
52 references: Vec<TaskId>,
53}
54
55impl TaskBuilder {
56 pub fn id(mut self, id: TaskId) -> Self {
57 self.id = Some(id);
58 self
59 }
60
61 pub fn title(mut self, title: impl Into<String>) -> Self {
62 self.title = Some(title.into());
63 self
64 }
65
66 pub fn status(mut self, status: Status) -> Self {
67 self.status = Some(status);
68 self
69 }
70
71 pub fn column(mut self, column: impl Into<String>) -> Self {
72 self.column = Some(ColumnRef::Name(column.into()));
73 self
74 }
75
76 pub fn metadata(mut self, metadata: Metadata) -> Self {
77 self.metadata = Some(metadata);
78 self
79 }
80
81 pub fn depends_on(mut self, task_id: TaskId) -> Self {
82 self.dependencies.push(task_id);
83 self
84 }
85
86 pub fn with_dependencies(mut self, deps: Vec<TaskId>) -> Self {
87 self.dependencies = deps;
88 self
89 }
90
91 pub fn references(mut self, task_id: TaskId) -> Self {
92 self.references.push(task_id);
93 self
94 }
95
96 pub fn build(self) -> Result<Task, String> {
97 let id = self.id.ok_or("Task ID is required")?;
98 let title = self.title.ok_or("Task title is required")?;
99 let now = chrono::Utc::now();
100
101 Ok(Task {
102 id,
103 title,
104 status: self.status.unwrap_or_default(),
105 column: self.column.unwrap_or_else(|| ColumnRef::Name("Todo".to_string())),
106 metadata: self.metadata.unwrap_or_default(),
107 dependencies: self.dependencies,
108 references: self.references,
109 created_at: now,
110 updated_at: now,
111 })
112 }
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, TS)]
116#[ts(export)]
117#[serde(rename_all = "lowercase")]
118pub enum Status {
119 Todo,
120 InProgress,
121 Ready,
122 Done,
123 Blocked,
124 Urgent,
125}
126
127impl Default for Status {
128 fn default() -> Self {
129 Self::Todo
130 }
131}
132
133impl std::str::FromStr for Status {
134 type Err = String;
135
136 fn from_str(s: &str) -> Result<Self, Self::Err> {
137 match s.to_lowercase().as_str() {
138 "todo" | "t" => Ok(Self::Todo),
139 "inprogress" | "in-progress" | "ip" | "doing" => Ok(Self::InProgress),
140 "ready" | "r" => Ok(Self::Ready),
141 "done" | "d" | "completed" | "complete" => Ok(Self::Done),
142 "blocked" | "b" => Ok(Self::Blocked),
143 "urgent" | "u" => Ok(Self::Urgent),
144 _ => Err(format!("Invalid status: {}. Valid values: todo, inprogress, ready, done, blocked, urgent", s)),
145 }
146 }
147}
148
149impl std::fmt::Display for Status {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 match self {
152 Status::Todo => write!(f, "Todo"),
153 Status::InProgress => write!(f, "In Progress"),
154 Status::Ready => write!(f, "Ready"),
155 Status::Done => write!(f, "Done"),
156 Status::Blocked => write!(f, "Blocked"),
157 Status::Urgent => write!(f, "Urgent"),
158 }
159 }
160}
161
162#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, TS)]
163#[ts(export)]
164pub struct Dependency {
165 pub from: TaskId,
166 pub to: TaskId,
167 pub dependency_type: DependencyType,
168}
169
170#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
171#[ts(export)]
172pub enum DependencyType {
173 Blocks,
174 DependsOn,
175 Related,
176}