Skip to main content

tdm_server_rust/repository/
questionnaire_repo.rs

1//! 接稿问卷数据访问层 (Questionnaire Repository)
2//!
3//! 封装 `questionnaire` 表的提交与多维筛选查询(SeaORM)。
4
5use crate::db::DbConn;
6use crate::entity::questionnaire::Questionnaire;
7use crate::sea_entity::{mangaepisodedetail, mangaepisodetb, memberpost, questionnaire};
8use chrono::{DateTime, Utc};
9use sea_orm::{
10    ActiveModelTrait, ColumnTrait, ConnectionTrait, DatabaseConnection, DbBackend, EntityTrait,
11    JoinType, QueryFilter, QueryOrder, QuerySelect, RelationTrait, Set, Statement, Value,
12};
13use sqlx::postgres::PgPool;
14use std::collections::HashMap;
15
16/// 问卷仓储
17pub struct QuestionnaireRepository {
18    /// SeaORM 数据库连接
19    db: DbConn,
20}
21
22impl QuestionnaireRepository {
23    /// 从 `PgPool` 构造
24    pub fn new(pool: PgPool) -> Self {
25        Self {
26            db: crate::db::from_sqlx_pool(pool),
27        }
28    }
29
30    /// 从 `DatabaseConnection` 构造
31    pub fn from_db(db: DatabaseConnection) -> Self {
32        Self { db }
33    }
34
35    /// 按组员 ID 查询问卷
36    #[tracing::instrument(skip_all, level = "debug")]
37    pub async fn get_by_member_id(
38        &self,
39        member_id: i32,
40    ) -> crate::error::ApiResult<Option<Questionnaire>> {
41        let row = questionnaire::Entity::find()
42            .filter(questionnaire::Column::MemberId.eq(member_id as i64))
43            .one(&self.db)
44            .await?;
45        Ok(row.map(model_to_questionnaire))
46    }
47
48    /// 条件列表查询(不含岗位筛选)
49    #[tracing::instrument(skip_all, level = "debug")]
50    pub async fn list_all(
51        &self,
52        willingness: Option<i16>,
53        length: Option<&str>,
54        frequency: Option<i16>,
55        member_id: Option<i32>,
56    ) -> crate::error::ApiResult<Vec<Questionnaire>> {
57        let mut q = questionnaire::Entity::find();
58        if let Some(v) = willingness {
59            q = q.filter(questionnaire::Column::Willingness.eq(v));
60        }
61        if let Some(v) = length {
62            q = q.filter(questionnaire::Column::AcceptableLengths.eq(v));
63        }
64        if let Some(v) = frequency {
65            q = q.filter(questionnaire::Column::UpdateFrequency.eq(v));
66        }
67        if let Some(v) = member_id {
68            q = q.filter(questionnaire::Column::MemberId.eq(v as i64));
69        }
70        let rows = q.all(&self.db).await?;
71        Ok(rows.into_iter().map(model_to_questionnaire).collect())
72    }
73
74    /// 按岗位筛选问卷列表
75    #[tracing::instrument(skip_all, level = "debug")]
76    pub async fn list_by_post(
77        &self,
78        post_id: i32,
79        willingness: Option<i16>,
80        length: Option<&str>,
81        frequency: Option<i16>,
82        member_id: Option<i32>,
83    ) -> crate::error::ApiResult<Vec<Questionnaire>> {
84        let member_ids: Vec<i64> = memberpost::Entity::find()
85            .filter(memberpost::Column::PostId.eq(post_id))
86            .all(&self.db)
87            .await?
88            .into_iter()
89            .map(|m| m.member_id as i64)
90            .collect();
91        if member_ids.is_empty() {
92            return Ok(vec![]);
93        }
94        let mut q =
95            questionnaire::Entity::find().filter(questionnaire::Column::MemberId.is_in(member_ids));
96        if let Some(v) = willingness {
97            q = q.filter(questionnaire::Column::Willingness.eq(v));
98        }
99        if let Some(v) = length {
100            q = q.filter(questionnaire::Column::AcceptableLengths.eq(v));
101        }
102        if let Some(v) = frequency {
103            q = q.filter(questionnaire::Column::UpdateFrequency.eq(v));
104        }
105        if let Some(v) = member_id {
106            q = q.filter(questionnaire::Column::MemberId.eq(v as i64));
107        }
108        let rows = q.all(&self.db).await?;
109        Ok(rows.into_iter().map(model_to_questionnaire).collect())
110    }
111
112    /// 新增问卷
113    #[tracing::instrument(skip_all, level = "debug")]
114    pub async fn insert(&self, q: &Questionnaire) -> crate::error::ApiResult<i64> {
115        let now = chrono::Utc::now().naive_utc();
116        let model = questionnaire::ActiveModel {
117            member_id: Set(q.member_id.unwrap_or(0) as i64),
118            willingness: Set(q.willingness.unwrap_or(0)),
119            acceptable_lengths: Set(q.acceptable_lengths.clone()),
120            update_frequency: Set(q.update_frequency),
121            preferred_genres: Set(q.preferred_genres.clone()),
122            avoid_topics: Set(q.avoid_topics.clone()),
123            note: Set(q.note.clone()),
124            remark: Set(q.remark.clone()),
125            created_at: Set(now),
126            updated_at: Set(now),
127            ..Default::default()
128        };
129        let inserted = model.insert(&self.db).await?;
130        Ok(inserted.id)
131    }
132
133    /// 更新问卷
134    #[tracing::instrument(skip_all, level = "debug")]
135    pub async fn update(&self, q: &Questionnaire) -> crate::error::ApiResult<()> {
136        let id =
137            q.id.ok_or_else(|| crate::error::AppError::business("问卷 ID 不能为空"))?;
138        let model = questionnaire::ActiveModel {
139            id: Set(id),
140            willingness: Set(q.willingness.unwrap_or(0)),
141            acceptable_lengths: Set(q.acceptable_lengths.clone()),
142            update_frequency: Set(q.update_frequency),
143            preferred_genres: Set(q.preferred_genres.clone()),
144            avoid_topics: Set(q.avoid_topics.clone()),
145            note: Set(q.note.clone()),
146            remark: Set(q.remark.clone()),
147            updated_at: Set(chrono::Utc::now().naive_utc()),
148            ..Default::default()
149        };
150        model.update(&self.db).await?;
151        Ok(())
152    }
153
154    /// 查询翻译岗最近接稿时间
155    #[tracing::instrument(skip_all, level = "debug")]
156    pub async fn select_last_assigned_time(
157        &self,
158        member_id: i32,
159    ) -> crate::error::ApiResult<Option<DateTime<Utc>>> {
160        let row = mangaepisodedetail::Entity::find()
161            .join(
162                JoinType::InnerJoin,
163                mangaepisodedetail::Relation::Mangaepisodetb.def(),
164            )
165            .filter(mangaepisodetb::Column::TranslatorId.eq(member_id))
166            .filter(mangaepisodedetail::Column::TranslatorSetupTime.is_not_null())
167            .order_by_desc(mangaepisodedetail::Column::TranslatorSetupTime)
168            .one(&self.db)
169            .await?;
170        Ok(row.and_then(|r| r.translator_setup_time.map(|t| t.and_utc())))
171    }
172
173    /// 批量查询翻译岗最近接稿时间。
174    #[tracing::instrument(skip_all, level = "debug")]
175    pub async fn select_last_assigned_times(
176        &self,
177        member_ids: &[i32],
178    ) -> crate::error::ApiResult<HashMap<i32, DateTime<Utc>>> {
179        if member_ids.is_empty() {
180            return Ok(HashMap::new());
181        }
182        let mut ids = member_ids.to_vec();
183        ids.sort_unstable();
184        ids.dedup();
185        let placeholders = (1..=ids.len())
186            .map(|i| format!("${i}"))
187            .collect::<Vec<_>>()
188            .join(", ");
189        let sql = format!(
190            r#"SELECT e."translatorId" AS member_id,
191                MAX(d."translatorSetupTime") AS last_assigned_time
192            FROM mangaepisodedetail d
193            INNER JOIN mangaepisodetb e ON e."Id" = d."episodeId"
194            WHERE e."translatorId" IN ({placeholders})
195              AND d."translatorSetupTime" IS NOT NULL
196            GROUP BY e."translatorId""#
197        );
198        let values: Vec<Value> = ids.into_iter().map(Value::from).collect();
199        let rows = self
200            .db
201            .query_all(Statement::from_sql_and_values(
202                DbBackend::Postgres,
203                &sql,
204                values,
205            ))
206            .await?;
207        let mut out = HashMap::new();
208        for row in rows {
209            let member_id: i32 = row.try_get("", "member_id")?;
210            let assigned: Option<chrono::NaiveDateTime> = row.try_get("", "last_assigned_time")?;
211            if let Some(assigned) = assigned {
212                out.insert(member_id, assigned.and_utc());
213            }
214        }
215        Ok(out)
216    }
217}
218
219/// Model 映射为问卷 DTO
220fn model_to_questionnaire(m: questionnaire::Model) -> Questionnaire {
221    Questionnaire {
222        id: Some(m.id),
223        member_id: Some(m.member_id as i32),
224        willingness: Some(m.willingness),
225        acceptable_lengths: m.acceptable_lengths,
226        update_frequency: m.update_frequency,
227        preferred_genres: m.preferred_genres,
228        avoid_topics: m.avoid_topics,
229        note: m.note,
230        remark: m.remark,
231        last_assigned_time: m.last_assigned_time.map(|t| t.and_utc()),
232        created_at: Some(m.created_at.and_utc()),
233        updated_at: Some(m.updated_at.and_utc()),
234    }
235}