Skip to main content

tdm_server_rust/entity/
episode.rs

1//! 话数相关实体 (Episode Entities)
2//!
3//! 定义话数管理的全部数据结构:
4//! - 列表/详情 VO: [`EpisodeSimpleListVo`], [`EpisodeListVo`], [`EpisodeDetailVo`]
5//! - 上传相关: [`UploadPageVo`], [`PublishLinkRequest`], [`EpisodeEditDto`]
6//! - 统计: [`Statistics`], [`MemberStatistics`]
7//! - 任务追踪: [`EpisodeTask`], [`MemberTaskCount`], [`TaskTrackingResponse`]
8//! - RSS/发布: [`PendingMangaTask`], [`PendingEpisode`], [`MangaEpisodeTb`]
9
10use chrono::{DateTime, Utc};
11use serde::{Deserialize, Serialize};
12
13/// 话数简单列表 VO
14#[derive(Debug, Clone, Serialize, Deserialize, Default)]
15#[serde(rename_all = "camelCase")]
16pub struct EpisodeSimpleListVo {
17    /// 话数 ID
18    pub id: i32,
19    /// 话数标签
20    pub manga_episode: Option<String>,
21    /// 话数名
22    pub manga_episode_name: Option<String>,
23    /// 翻译文件
24    pub translator_file: Option<String>,
25    /// 校对文件
26    pub proofreader_file: Option<String>,
27    /// 时轴文件
28    pub timer_file: Option<String>,
29    /// 图源 OSS ID
30    pub provider_file_oss_id: Option<i32>,
31    /// 翻译稿 OSS ID
32    pub translator_file_oss_id: Option<i32>,
33    /// 校对稿 OSS ID
34    pub proofreader_file_oss_id: Option<i32>,
35    /// 嵌字稿 OSS ID
36    pub letterer_file_oss_id: Option<i32>,
37    /// 时轴 OSS ID
38    pub timer_file_oss_id: Option<i32>,
39    /// 发布链接
40    pub publish_link: Option<String>,
41}
42
43/// 话数列表 VO
44#[derive(Debug, Clone, Serialize, Deserialize, Default)]
45#[serde(rename_all = "camelCase")]
46pub struct EpisodeListVo {
47    /// 话数 ID
48    pub id: i32,
49    /// 漫画 ID
50    pub manga_id: i32,
51    /// 话数标签
52    pub manga_episode: Option<String>,
53    /// 话数名
54    pub manga_episode_name: Option<String>,
55    /// 图源 ID
56    pub provider_id: Option<i32>,
57    /// 翻译 ID
58    pub translator_id: Option<i32>,
59    /// 校对 ID
60    pub proofreader_id: Option<i32>,
61    /// 嵌字 ID
62    pub letterer_id: Option<i32>,
63    /// 时轴 ID
64    pub timer_id: Option<i32>,
65    /// 审稿 ID
66    pub reviewer_id: Option<i32>,
67    /// 设立时间
68    pub setup_time: Option<DateTime<Utc>>,
69    /// 更新时间
70    pub update_time: Option<DateTime<Utc>>,
71    /// 翻译文件
72    pub translator_file: Option<String>,
73    /// 校对文件
74    pub proofreader_file: Option<String>,
75    /// 时轴文件
76    pub timer_file: Option<String>,
77    /// 发布链接
78    pub publish_link: Option<String>,
79    /// 图源 OSS ID
80    pub provider_file_oss_id: Option<i32>,
81    /// 翻译稿 OSS ID
82    pub translator_file_oss_id: Option<i32>,
83    /// 校对稿 OSS ID
84    pub proofreader_file_oss_id: Option<i32>,
85    /// 嵌字稿 OSS ID
86    pub letterer_file_oss_id: Option<i32>,
87    /// 时轴 OSS ID
88    pub timer_file_oss_id: Option<i32>,
89    /// 翻译接稿时间
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub translator_setup_time: Option<DateTime<Utc>>,
92    /// 翻译交稿时间
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub translator_update_time: Option<DateTime<Utc>>,
95    /// 校对接稿时间
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub proofreader_setup_time: Option<DateTime<Utc>>,
98    /// 校对交稿时间
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub proofreader_update_time: Option<DateTime<Utc>>,
101    /// 嵌字接稿时间
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub letterer_setup_time: Option<DateTime<Utc>>,
104    /// 嵌字交稿时间
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub letterer_update_time: Option<DateTime<Utc>>,
107    /// 时轴接稿时间
108    #[serde(skip_serializing_if = "Option::is_none")]
109    pub timer_setup_time: Option<DateTime<Utc>>,
110    /// 时轴交稿时间
111    #[serde(skip_serializing_if = "Option::is_none")]
112    pub timer_update_time: Option<DateTime<Utc>>,
113    /// 审稿接稿时间
114    #[serde(skip_serializing_if = "Option::is_none")]
115    pub reviewer_setup_time: Option<DateTime<Utc>>,
116    /// 审稿交稿时间
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub reviewer_update_time: Option<DateTime<Utc>>,
119    /// 图源组员名
120    #[serde(skip_serializing_if = "Option::is_none")]
121    pub provider_name: Option<String>,
122    /// 翻译组员名
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub translator_name: Option<String>,
125    /// 校对组员名
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub proofreader_name: Option<String>,
128    /// 嵌字组员名
129    #[serde(skip_serializing_if = "Option::is_none")]
130    pub letterer_name: Option<String>,
131    /// 时轴组员名
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub timer_name: Option<String>,
134    /// 审稿组员名
135    #[serde(skip_serializing_if = "Option::is_none")]
136    pub reviewer_name: Option<String>,
137    /// 图源文件名
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub provider_filename: Option<String>,
140    /// 翻译稿文件名
141    #[serde(skip_serializing_if = "Option::is_none")]
142    pub translator_filename: Option<String>,
143    /// 校对稿文件名
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub proofreader_filename: Option<String>,
146    /// 嵌字稿文件名
147    #[serde(skip_serializing_if = "Option::is_none")]
148    pub letterer_filename: Option<String>,
149    /// 时轴文件名
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub timer_filename: Option<String>,
152}
153
154/// 话数详情 VO
155#[derive(Debug, Clone, Serialize, Deserialize, Default)]
156#[serde(rename_all = "camelCase")]
157pub struct EpisodeDetailVo {
158    /// 话数 ID
159    pub id: i32,
160    /// 漫画 ID
161    pub manga_id: i32,
162    /// 话数标签
163    pub manga_episode: Option<String>,
164    /// 话数名
165    pub manga_episode_name: Option<String>,
166    /// 图源 ID
167    pub provider_id: Option<i32>,
168    /// 翻译 ID
169    pub translator_id: Option<i32>,
170    /// 校对 ID
171    pub proofreader_id: Option<i32>,
172    /// 嵌字 ID
173    pub letterer_id: Option<i32>,
174    /// 时轴 ID
175    pub timer_id: Option<i32>,
176    /// 审稿 ID
177    pub reviewer_id: Option<i32>,
178    /// 设立时间
179    pub setup_time: Option<DateTime<Utc>>,
180    /// 更新时间
181    pub update_time: Option<DateTime<Utc>>,
182    /// 发布链接
183    pub publish_link: Option<String>,
184}
185
186/// 最新话 VO
187#[derive(Debug, Clone, Serialize, Deserialize, Default)]
188#[serde(rename_all = "camelCase")]
189pub struct NewestEpisodeVo {
190    /// 话数标签
191    pub manga_episode: Option<String>,
192    /// 翻译 ID
193    pub translator_id: Option<i32>,
194    /// 校对 ID
195    pub proofreader_id: Option<i32>,
196    /// 嵌字 ID
197    pub letterer_id: Option<i32>,
198    /// 审稿 ID
199    pub reviewer_id: Option<i32>,
200}
201
202/// 话数编辑 DTO (Episode Edit Data Transfer Object)
203///
204/// 用于创建/批量创建/更新话数。
205/// - `manga_episode_end`: 批量创建时的结束话数标签
206/// - 各岗位 ID 字段若为 None,表示不修改该岗位分配
207///
208/// 对应 Java `EpisodeEditDTO`。
209#[derive(Debug, Clone, Deserialize, Serialize)]
210#[serde(rename_all = "camelCase")]
211pub struct EpisodeEditDto {
212    /// 话数 ID
213    #[serde(default, deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose")]
214    pub id: Option<i32>,
215    /// 漫画 ID
216    #[serde(default, deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose")]
217    pub manga_id: Option<i32>,
218    /// 话数标签
219    pub manga_episode: Option<String>,
220    /// 批量结束话数
221    pub manga_episode_end: Option<String>,
222    /// 话数名
223    pub manga_episode_name: Option<String>,
224    /// 图源 ID
225    #[serde(default, deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose")]
226    pub provider_id: Option<i32>,
227    /// 翻译 ID
228    #[serde(default, deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose")]
229    pub translator_id: Option<i32>,
230    /// 校对 ID
231    #[serde(default, deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose")]
232    pub proofreader_id: Option<i32>,
233    /// 嵌字 ID
234    #[serde(default, deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose")]
235    pub letterer_id: Option<i32>,
236    /// 时轴 ID
237    #[serde(default, deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose")]
238    pub timer_id: Option<i32>,
239    /// 审稿 ID
240    #[serde(default, deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose")]
241    pub reviewer_id: Option<i32>,
242    /// 发布链接
243    pub publish_link: Option<String>,
244}
245
246/// 发布链接请求
247#[derive(Debug, Clone, Deserialize, Serialize)]
248#[serde(rename_all = "camelCase")]
249pub struct PublishLinkRequest {
250    /// 话数 ID
251    pub id: i32,
252    /// 发布链接
253    pub publish_link: Option<String>,
254}
255
256/// 上传页 VO
257#[derive(Debug, Clone, Serialize, Deserialize, Default)]
258#[serde(rename_all = "camelCase")]
259pub struct UploadPageVo {
260    /// 话数 ID
261    pub id: i32,
262    /// 漫画 ID
263    pub manga_id: i32,
264    /// 漫画译名
265    pub manga_name: Option<String>,
266    /// 话数标签
267    pub manga_episode: Option<String>,
268    /// 更新时间
269    pub update_time: Option<DateTime<Utc>>,
270    /// 翻译文件
271    pub translator_file: Option<String>,
272    /// 校对文件
273    pub proofreader_file: Option<String>,
274    /// 时轴文件
275    pub timer_file: Option<String>,
276    /// 图源 ID
277    pub provider_id: Option<i32>,
278    /// 翻译 ID
279    pub translator_id: Option<i32>,
280    /// 校对 ID
281    pub proofreader_id: Option<i32>,
282    /// 嵌字 ID
283    pub letterer_id: Option<i32>,
284    /// 时轴 ID
285    pub timer_id: Option<i32>,
286    /// 审稿 ID
287    pub reviewer_id: Option<i32>,
288    /// 图源组员名
289    #[serde(skip_serializing_if = "Option::is_none")]
290    pub provider_name: Option<String>,
291    /// 翻译组员名
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub translator_name: Option<String>,
294    /// 校对组员名
295    #[serde(skip_serializing_if = "Option::is_none")]
296    pub proofreader_name: Option<String>,
297    /// 嵌字组员名
298    #[serde(skip_serializing_if = "Option::is_none")]
299    pub letterer_name: Option<String>,
300    /// 时轴组员名
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub timer_name: Option<String>,
303    /// 审稿组员名
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub reviewer_name: Option<String>,
306    /// 图源 OSS ID
307    #[serde(skip_serializing_if = "Option::is_none")]
308    pub provider_file_oss_id: Option<i32>,
309    /// 翻译稿 OSS ID
310    #[serde(skip_serializing_if = "Option::is_none")]
311    pub translator_file_oss_id: Option<i32>,
312    /// 校对稿 OSS ID
313    #[serde(skip_serializing_if = "Option::is_none")]
314    pub proofreader_file_oss_id: Option<i32>,
315    /// 嵌字稿 OSS ID
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub letterer_file_oss_id: Option<i32>,
318    /// 时轴 OSS ID
319    #[serde(skip_serializing_if = "Option::is_none")]
320    pub timer_file_oss_id: Option<i32>,
321    /// 图源文件名
322    #[serde(skip_serializing_if = "Option::is_none")]
323    pub provider_filename: Option<String>,
324    /// 翻译稿文件名
325    #[serde(skip_serializing_if = "Option::is_none")]
326    pub translator_filename: Option<String>,
327    /// 校对稿文件名
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub proofreader_filename: Option<String>,
330    /// 嵌字稿文件名
331    #[serde(skip_serializing_if = "Option::is_none")]
332    pub letterer_filename: Option<String>,
333    /// 时轴文件名
334    #[serde(skip_serializing_if = "Option::is_none")]
335    pub timer_filename: Option<String>,
336    /// 翻译邮箱
337    #[serde(skip_serializing_if = "Option::is_none")]
338    pub email: Option<String>,
339    /// 当前岗位邮箱
340    #[serde(skip_serializing_if = "Option::is_none")]
341    pub email_now: Option<String>,
342}
343
344/// 统计(对齐 Java Statistics)
345#[derive(Debug, Clone, Serialize, Deserialize, Default)]
346#[serde(rename_all = "camelCase")]
347pub struct Statistics {
348    /// 翻译完成数
349    #[serde(default)]
350    pub translator_count: i32,
351    /// 校对完成数
352    #[serde(default)]
353    pub proofreader_count: i32,
354    /// 嵌字完成数
355    #[serde(default)]
356    pub letterer_count: i32,
357    /// 审稿完成数
358    #[serde(default)]
359    pub reviewer_count: i32,
360}
361
362/// 组员统计(对齐 Java MemberStatistics)
363#[derive(Debug, Clone, Serialize, Deserialize, Default)]
364#[serde(rename_all = "camelCase")]
365pub struct MemberStatistics {
366    /// 组员用户名
367    pub user_name: Option<String>,
368    /// 组员 ID
369    pub user_id: Option<String>,
370    /// 翻译完成数
371    pub translator_count: i32,
372    /// 校对完成数
373    pub proofreader_count: i32,
374    /// 嵌字完成数
375    pub letterer_count: i32,
376    /// 时轴完成数
377    pub timer_count: i32,
378    /// 审稿完成数
379    pub reviewer_count: i32,
380}
381
382/// 稿件任务(对齐 Java EpisodeTask)
383#[derive(Debug, Clone, Serialize, Deserialize, Default)]
384#[serde(rename_all = "camelCase")]
385pub struct EpisodeTask {
386    /// 漫画 ID
387    pub manga_id: Option<i32>,
388    /// 话数 ID
389    pub episode_id: Option<i32>,
390    /// 话数标签
391    pub manga_episode: Option<String>,
392    /// 话数名
393    pub episode_name: Option<String>,
394    /// 负责组员 ID
395    pub member_id: Option<i32>,
396    /// 接稿时间
397    pub setup_time: Option<DateTime<Utc>>,
398    /// 前一岗位交稿时间
399    pub previous_update_time: Option<DateTime<Utc>>,
400    /// 任务类型(PostEnum 名)
401    pub post: Option<String>,
402}
403
404/// 任务追踪(对齐 Java MemberTaskCount)
405#[derive(Debug, Clone, Serialize, Deserialize, Default)]
406#[serde(rename_all = "camelCase")]
407pub struct MemberTaskCount {
408    /// 组员用户名
409    pub user_name: Option<String>,
410    /// 组员 ID
411    pub user_id: Option<String>,
412    /// 职阶
413    pub intern: Option<i32>,
414    /// 邮箱
415    pub email: Option<String>,
416    /// 最后交稿时间
417    pub last_update_time: Option<String>,
418    /// 常驻数
419    pub stationed_count: i32,
420    /// 翻译任务数
421    pub translator_count: i32,
422    /// 校对任务数
423    pub proofreader_count: i32,
424    /// 嵌字任务数
425    pub letterer_count: i32,
426    /// 时轴任务数
427    pub timer_count: i32,
428    /// 审稿任务数
429    pub reviewer_count: i32,
430}
431
432impl MemberTaskCount {
433    /// 总任务数
434    pub fn total_count(&self) -> i32 {
435        self.translator_count
436            + self.proofreader_count
437            + self.letterer_count
438            + self.timer_count
439            + self.reviewer_count
440    }
441}
442
443/// 任务追踪响应 (Task Tracking Response)
444///
445/// 按岗位分组的话数任务列表,用于组员任务看板。
446///
447/// 对应 Java `TaskTrackingResponse`。
448#[derive(Debug, Clone, Serialize, Deserialize, Default)]
449#[serde(rename_all = "camelCase")]
450pub struct TaskTrackingResponse {
451    /// 翻译稿件列表
452    pub translator_task_list: Vec<EpisodeTask>,
453    /// 校对稿件列表
454    pub proofreader_task_list: Vec<EpisodeTask>,
455    /// 嵌字稿件列表
456    pub letterer_task_list: Vec<EpisodeTask>,
457    /// 时轴稿件列表
458    pub timer_task_list: Vec<EpisodeTask>,
459    /// 审稿稿件列表
460    pub reviewer_task_list: Vec<EpisodeTask>,
461    /// 发布稿件列表
462    pub publish_task_list: Vec<EpisodeTask>,
463    /// 全部稿件列表
464    pub all_task_list: Vec<EpisodeTask>,
465}
466
467impl TaskTrackingResponse {
468    /// 空列表初始化
469    pub fn empty() -> Self {
470        Self::default()
471    }
472}
473
474/// 话数 POJO(Member 模块用)
475#[derive(Debug, Clone, Serialize, Deserialize)]
476#[serde(rename_all = "camelCase")]
477pub struct MangaEpisode {
478    /// 话数 ID
479    #[serde(rename = "Id")]
480    pub id: i32,
481    /// 漫画 ID
482    pub manga_id: i32,
483    /// 话数标签
484    pub manga_episode: Option<String>,
485    /// 漫画名
486    pub manga_name: Option<String>,
487    /// 分类
488    pub category: Option<i16>,
489}
490
491/// 待发布漫画任务 (Pending Manga Task)
492///
493/// 用于 RSS 定时任务扫描:查找有已完成审稿但尚未发布话数的漫画。
494///
495/// - `mangatb`: 漫画基本信息
496/// - `newest_manga_episode`: 最新已发布话数标签
497/// - `pending_episode_list`: 审稿完成待发布的话数列表
498///
499/// 对应 Java `PendingMangaTask`。
500#[derive(Debug, Clone, Serialize, Deserialize)]
501#[serde(rename_all = "camelCase")]
502pub struct PendingMangaTask {
503    /// 漫画信息
504    pub mangatb: crate::entity::manga::Mangatb,
505    /// 最新已发布话数标签
506    pub newest_manga_episode: Option<String>,
507    /// 待处理话数列表
508    pub pending_episode_list: Vec<PendingEpisode>,
509}
510/// 话数数据库行 (Manga Episode Table Row)
511///
512/// 映射 `t_manga_episode` 表,用于仓储层行映射和 [`PendingEpisode`]。
513#[derive(Debug, Clone, Serialize, Deserialize, Default)]
514#[serde(rename_all = "camelCase")]
515pub struct MangaEpisodeTb {
516    /// 话数 ID
517    pub id: i32,
518    /// 漫画 ID
519    pub manga_id: i32,
520    /// 话数标签
521    pub manga_episode: Option<String>,
522    /// 话数名
523    pub manga_episode_name: Option<String>,
524    /// 图源 ID
525    pub provider_id: Option<i32>,
526    /// 翻译 ID
527    pub translator_id: Option<i32>,
528    /// 校对 ID
529    pub proofreader_id: Option<i32>,
530    /// 嵌字 ID
531    pub letterer_id: Option<i32>,
532    /// 时轴 ID
533    pub timer_id: Option<i32>,
534    /// 审稿 ID
535    pub reviewer_id: Option<i32>,
536    /// 设立时间
537    pub setup_time: Option<DateTime<Utc>>,
538    /// 更新时间
539    pub update_time: Option<DateTime<Utc>>,
540    /// 翻译文件
541    pub translator_file: Option<String>,
542    /// 校对文件
543    pub proofreader_file: Option<String>,
544    /// 时轴文件
545    pub timer_file: Option<String>,
546    /// 发布链接
547    pub publish_link: Option<String>,
548    /// 图源 OSS ID
549    pub provider_file_oss_id: Option<i32>,
550    /// 翻译稿 OSS ID
551    pub translator_file_oss_id: Option<i32>,
552    /// 校对稿 OSS ID
553    pub proofreader_file_oss_id: Option<i32>,
554    /// 嵌字稿 OSS ID
555    pub letterer_file_oss_id: Option<i32>,
556    /// 时轴 OSS ID
557    pub timer_file_oss_id: Option<i32>,
558}
559
560/// 待发布单话(对齐 Java PendingEpisode)
561#[derive(Debug, Clone, Serialize, Deserialize)]
562#[serde(rename_all = "camelCase")]
563pub struct PendingEpisode {
564    /// 单话信息
565    pub mangaepisodetb: MangaEpisodeTb,
566    /// 当前状态(reviewer / publisher)
567    pub status: String,
568    /// 是否下一个待发布话
569    pub next_publish: bool,
570}