Skip to main content

tdm_server_rust/web/
task_tracking_controller.rs

1//! 稿件监控接口 (Task Tracking Controller)
2//!
3//! 组员任务看板数据查询。
4//! 对应 Java TaskTrackingController。
5
6use crate::utils::query_deserialize::{
7    de_opt_csv_i32, de_opt_csv_intern, de_opt_csv_string, de_opt_datetime, de_opt_string, de_page,
8    de_page_size,
9};
10use crate::{
11    app::AppState,
12    common::{
13        page_bean::PageBean,
14        result::{json_bytes_response, ResultBody},
15    },
16    entity::episode::{PendingMangaTask, WorkflowEpisodeItem},
17    error::ApiResult,
18    repository::workflow_filter::WorkflowEpisodeQuery,
19    service::task_tracking_service::TaskTrackingService,
20};
21use axum::{
22    extract::{Query, State},
23    response::Response,
24    routing::get,
25    Router,
26};
27use serde::Deserialize;
28
29/// 工序汇总筛选查询参数
30#[derive(Debug, Deserialize)]
31#[serde(rename_all = "camelCase")]
32pub struct WorkflowEpisodesQuery {
33    /// 已经 done / 尚未 undone
34    pub status: String,
35    /// 分发 assign / 完成 complete
36    pub aspect: String,
37    /// 环节,逗号分隔
38    pub steps: String,
39    /// 漫画译名模糊匹配
40    #[serde(default, deserialize_with = "de_opt_string")]
41    pub manga_tran_name: Option<String>,
42    /// 漫画 ID,逗号分隔
43    #[serde(default, deserialize_with = "de_opt_csv_i32")]
44    pub manga_ids: Option<Vec<i32>>,
45    /// 组员 ID,逗号分隔(-1 表示未派稿)
46    #[serde(default, deserialize_with = "de_opt_csv_i32")]
47    pub member_ids: Option<Vec<i32>>,
48    /// 职阶,逗号分隔(空段表示职阶为空)
49    #[serde(default, deserialize_with = "de_opt_csv_intern")]
50    pub intern_ids: Option<Vec<Option<i32>>>,
51    /// 前一环节交稿时间不早于(ISO8601)
52    #[serde(default, deserialize_with = "de_opt_datetime")]
53    pub previous_update_time_after: Option<chrono::DateTime<chrono::Utc>>,
54    /// 环节筛选,逗号分隔
55    #[serde(default, deserialize_with = "de_opt_csv_string")]
56    pub step_filter: Option<Vec<String>>,
57    /// 页码
58    #[serde(default, deserialize_with = "de_page")]
59    pub page: i32,
60    /// 每页条数
61    #[serde(default, deserialize_with = "de_page_size")]
62    pub page_size: i32,
63    /// 排序字段 previousUpdateTime / mangaId / memberId
64    #[serde(default, deserialize_with = "de_opt_string")]
65    pub sort_prop: Option<String>,
66    /// 排序方向 ascending / descending
67    #[serde(default, deserialize_with = "de_opt_string")]
68    pub sort_order: Option<String>,
69}
70
71/// 待发布漫画分页参数
72#[derive(Debug, Deserialize)]
73#[serde(rename_all = "camelCase")]
74pub struct PendingMangaQuery {
75    /// 页码
76    #[serde(default, deserialize_with = "de_page")]
77    pub page: i32,
78    /// 每页条数
79    #[serde(default, deserialize_with = "de_page_size")]
80    pub page_size: i32,
81    /// 漫画译名
82    #[serde(default, deserialize_with = "de_opt_string")]
83    pub manga_tran_name: Option<String>,
84}
85
86/// 稿件监控路由(挂载于 `/api/admin/taskTracking`)
87pub fn routes() -> Router<AppState> {
88    Router::new()
89        .route("/memberTaskCounts", get(get_member_task_counts))
90        .route("/episodeTasks", get(get_episode_tasks))
91        .route("/pendingMangaTasks", get(get_pending_manga_tasks))
92        .route("/workflowEpisodes", get(get_workflow_episodes))
93}
94
95/// 组员任务数量(缓存命中时直出预序列化 JSON)
96#[tracing::instrument(skip_all, level = "info")]
97pub async fn get_member_task_counts(State(state): State<AppState>) -> ApiResult<Response> {
98    let json = TaskTrackingService::get_member_task_counts_json(&state).await?;
99    Ok(json_bytes_response(json))
100}
101
102/// 待做稿件列表(缓存命中时直接返回预序列化 JSON)
103#[tracing::instrument(skip_all, level = "info")]
104pub async fn get_episode_tasks(State(state): State<AppState>) -> ApiResult<Response> {
105    let json = TaskTrackingService::get_episode_tasks_json(&state).await?;
106    Ok(json_bytes_response(json))
107}
108
109/// 待发布漫画分页
110#[tracing::instrument(skip_all, level = "info")]
111pub async fn get_pending_manga_tasks(
112    State(state): State<AppState>,
113    Query(q): Query<PendingMangaQuery>,
114) -> ApiResult<ResultBody<PageBean<PendingMangaTask>>> {
115    let data = TaskTrackingService::get_pending_manga_tasks(
116        &state,
117        q.page,
118        q.page_size,
119        q.manga_tran_name,
120    )
121    .await?;
122    Ok(ResultBody::success_data(data))
123}
124
125/// 工序汇总筛选分页
126#[tracing::instrument(skip_all, level = "info")]
127pub async fn get_workflow_episodes(
128    State(state): State<AppState>,
129    Query(q): Query<WorkflowEpisodesQuery>,
130) -> ApiResult<ResultBody<PageBean<WorkflowEpisodeItem>>> {
131    let steps: Vec<String> = q
132        .steps
133        .split(',')
134        .map(|s| s.trim().to_string())
135        .filter(|s| !s.is_empty())
136        .collect();
137    let query = WorkflowEpisodeQuery {
138        status: q.status,
139        aspect: q.aspect,
140        steps,
141        manga_tran_name: q.manga_tran_name,
142        manga_ids: q.manga_ids,
143        member_ids: q.member_ids,
144        intern_ids: q.intern_ids,
145        previous_update_time_after: q.previous_update_time_after,
146        step_filter: q.step_filter,
147        page: q.page,
148        page_size: q.page_size,
149        sort_prop: q.sort_prop,
150        sort_order: q.sort_order,
151    };
152    let data = TaskTrackingService::get_workflow_episodes(&state, query).await?;
153    Ok(ResultBody::success_data(data))
154}