@@ -13,9 +13,15 @@ use rusqlite::{params, Connection};
1313use crate :: db:: { unix_seconds, Db } ;
1414use crate :: error:: Result ;
1515use crate :: goal_schema:: {
16- Goal , GoalMilestone , NewGoal , NewMilestone , CRITERIA_GROUP_SETTLED , MILESTONE_DONE ,
16+ Goal , NewGoal , NewMilestone , CRITERIA_GROUP_SETTLED , MILESTONE_DONE ,
1717 MILESTONE_IN_PROGRESS , MILESTONE_PENDING ,
1818} ;
19+ use self :: milestone:: add_milestone_row;
20+
21+ // 子模块声明(AGENTS.md §3 单文件 ≤300 行;#[path] 使兄弟文件平铺在同目录,各自带 @ai-context)。
22+ /// 里程碑域:里程碑 CRUD + 三写路径共用的插入行 helper。
23+ #[ path = "db_goals_milestone.rs" ]
24+ mod milestone;
1925
2026/// 三表 DDL + 索引(幂等:CREATE TABLE IF NOT EXISTS;旧库升级自动补表)。
2127pub ( crate ) fn init ( conn : & Connection ) -> Result < ( ) > {
@@ -177,96 +183,6 @@ impl Db {
177183 } )
178184 }
179185
180- /// 目标里程碑清单(order_idx 升序——计划顺序即创建语义)。
181- pub fn list_milestones ( & self , goal_id : i64 ) -> Result < Vec < GoalMilestone > > {
182- self . with_conn ( |conn| {
183- let mut stmt = conn. prepare (
184- "SELECT * FROM goal_milestones WHERE goal_id = ?1 ORDER BY order_idx ASC, id ASC" ,
185- ) ?;
186- let rows = stmt. query_map ( params ! [ goal_id] , row_to_milestone) ?;
187- rows. collect :: < rusqlite:: Result < Vec < _ > > > ( ) . map_err ( Into :: into)
188- } )
189- }
190-
191- /// 新增里程碑(order_idx ≤0 时自动追加在末尾;criteria_type 白名单在命令层)。
192- pub fn add_milestone ( & self , goal_id : i64 , new : & NewMilestone ) -> Result < GoalMilestone > {
193- let now = unix_seconds ( ) ;
194- self . with_conn ( |conn| {
195- let idx = conn. query_row (
196- "SELECT COALESCE(MAX(order_idx), -1) + 1 FROM goal_milestones WHERE goal_id = ?1" ,
197- params ! [ goal_id] ,
198- |r| r. get :: < _ , i64 > ( 0 ) ,
199- ) ?;
200- let order = if new. order_idx > 0 { new. order_idx } else { idx } ;
201- conn. execute (
202- "INSERT INTO goal_milestones (goal_id, title, due_at, order_idx, status, criteria_type, ref_group_id, created_at)
203- VALUES (?1, ?2, ?3, ?4, 'pending', ?5, ?6, ?7)" ,
204- params ! [
205- goal_id, new. title, new. due_at, order, new. criteria_type,
206- new. ref_group_id, now
207- ] ,
208- ) ?;
209- Ok ( GoalMilestone {
210- id : conn. last_insert_rowid ( ) ,
211- goal_id,
212- title : new. title . clone ( ) ,
213- due_at : new. due_at ,
214- order_idx : order,
215- status : MILESTONE_PENDING . to_string ( ) ,
216- criteria_type : new. criteria_type . clone ( ) ,
217- ref_group_id : new. ref_group_id ,
218- completed_at : None ,
219- created_at : now,
220- } )
221- } )
222- }
223-
224- /// 更新里程碑(整段覆盖:标题/期限;顺序不在此改——增删即重排语义)。
225- pub fn update_milestone ( & self , id : i64 , title : & str , due_at : Option < i64 > ) -> Result < bool > {
226- self . with_conn ( |conn| {
227- let affected = conn. execute (
228- "UPDATE goal_milestones SET title = ?2, due_at = ?3 WHERE id = ?1" ,
229- params ! [ id, title, due_at] ,
230- ) ?;
231- Ok ( affected > 0 )
232- } )
233- }
234-
235- /// 按 id 读取里程碑(旧状态读取——状态流转前判据,不存在 → None)。
236- pub fn get_milestone ( & self , id : i64 ) -> Result < Option < GoalMilestone > > {
237- self . with_conn ( |conn| {
238- let mut stmt = conn. prepare ( "SELECT * FROM goal_milestones WHERE id = ?1" ) ?;
239- let mut rows = stmt. query_map ( params ! [ id] , row_to_milestone) ?;
240- match rows. next ( ) {
241- Some ( Ok ( m) ) => Ok ( Some ( m) ) ,
242- Some ( Err ( e) ) => Err ( e. into ( ) ) ,
243- None => Ok ( None ) ,
244- }
245- } )
246- }
247-
248- /// 删除里程碑(不存在 → false)。
249- pub fn delete_milestone ( & self , id : i64 ) -> Result < bool > {
250- self . with_conn ( |conn| {
251- let affected = conn. execute ( "DELETE FROM goal_milestones WHERE id = ?1" , params ! [ id] ) ?;
252- Ok ( affected > 0 )
253- } )
254- }
255-
256- /// 里程碑状态流转(done 写 completed_at;非完成态清空——状态机白名单在命令层)。
257- pub fn set_milestone_status ( & self , id : i64 , status : & str ) -> Result < bool > {
258- let now = unix_seconds ( ) ;
259- self . with_conn ( |conn| {
260- let affected = conn. execute (
261- "UPDATE goal_milestones SET status = ?2,
262- completed_at = CASE WHEN ?3 THEN ?4 ELSE NULL END
263- WHERE id = ?1" ,
264- params ! [ id, status, status == MILESTONE_DONE , now] ,
265- ) ?;
266- Ok ( affected > 0 )
267- } )
268- }
269-
270186 /// 绑定组到目标(UNIQUE 幂等:重复绑定返回 false;组不存在靠外键报错,
271187 /// 命令层先行校验)。
272188 pub fn bind_group ( & self , goal_id : i64 , group_id : i64 ) -> Result < bool > {
@@ -654,22 +570,6 @@ impl Db {
654570 }
655571}
656572
657- /// 里程碑插入行(create_goal 事务与 add_milestone 共用;criteria_type 默认 manual)。
658- fn add_milestone_row (
659- conn : & Connection ,
660- goal_id : i64 ,
661- m : & NewMilestone ,
662- order_idx : i64 ,
663- now : i64 ,
664- ) -> Result < ( ) > {
665- conn. execute (
666- "INSERT INTO goal_milestones (goal_id, title, due_at, order_idx, status, criteria_type, ref_group_id, created_at)
667- VALUES (?1, ?2, ?3, ?4, 'pending', ?5, ?6, ?7)" ,
668- params ! [ goal_id, m. title, m. due_at, order_idx, m. criteria_type, m. ref_group_id, now] ,
669- ) ?;
670- Ok ( ( ) )
671- }
672-
673573/// goals 行 → Goal。
674574fn row_to_goal ( row : & rusqlite:: Row < ' _ > ) -> rusqlite:: Result < Goal > {
675575 Ok ( Goal {
@@ -686,22 +586,6 @@ fn row_to_goal(row: &rusqlite::Row<'_>) -> rusqlite::Result<Goal> {
686586 } )
687587}
688588
689- /// goal_milestones 行 → GoalMilestone。
690- fn row_to_milestone ( row : & rusqlite:: Row < ' _ > ) -> rusqlite:: Result < GoalMilestone > {
691- Ok ( GoalMilestone {
692- id : row. get ( 0 ) ?,
693- goal_id : row. get ( 1 ) ?,
694- title : row. get ( 2 ) ?,
695- due_at : row. get ( 3 ) ?,
696- order_idx : row. get ( 4 ) ?,
697- status : row. get ( 5 ) ?,
698- criteria_type : row. get ( 6 ) ?,
699- ref_group_id : row. get ( 7 ) ?,
700- completed_at : row. get ( 8 ) ?,
701- created_at : row. get ( 9 ) ?,
702- } )
703- }
704-
705589#[ cfg( test) ]
706590#[ path = "db_goals_tests.rs" ]
707591mod tests;
0 commit comments