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/// 话数分类。
14#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, PartialEq, Eq)]
15#[serde(rename_all = "UPPERCASE")]
16pub enum EpisodeType {
17    /// 正篇单话。
18    #[default]
19    Main,
20    /// 番外、单行本加笔和发售特典。
21    Extra,
22}
23
24impl EpisodeType {
25    pub const fn as_str(self) -> &'static str {
26        match self {
27            Self::Main => "MAIN",
28            Self::Extra => "EXTRA",
29        }
30    }
31}
32
33/// 话数简单列表 VO
34#[derive(Debug, Clone, Serialize, Deserialize, Default)]
35#[serde(rename_all = "camelCase")]
36pub struct EpisodeSimpleListVo {
37    /// 话数 ID
38    pub id: i32,
39    /// 话数标签
40    pub manga_episode: Option<String>,
41    /// 话数名
42    pub manga_episode_name: Option<String>,
43    /// 话数分类
44    pub episode_type: EpisodeType,
45    /// 翻译文件
46    pub translator_file: Option<String>,
47    /// 校对文件
48    pub proofreader_file: Option<String>,
49    /// 时轴文件
50    pub timer_file: Option<String>,
51    /// 图源 OSS ID
52    pub provider_file_oss_id: Option<i32>,
53    /// 编辑器图源页面数量
54    pub editor_page_count: i64,
55    /// 翻译稿 OSS ID
56    pub translator_file_oss_id: Option<i32>,
57    /// 校对稿 OSS ID
58    pub proofreader_file_oss_id: Option<i32>,
59    /// 嵌字稿 OSS ID
60    pub letterer_file_oss_id: Option<i32>,
61    /// 时轴 OSS ID
62    pub timer_file_oss_id: Option<i32>,
63    /// 发布链接
64    pub publish_link: Option<String>,
65}
66
67/// 话数列表 VO
68#[derive(Debug, Clone, Serialize, Deserialize, Default)]
69#[serde(rename_all = "camelCase")]
70pub struct EpisodeListVo {
71    /// 话数 ID
72    pub id: i32,
73    /// 漫画 ID
74    pub manga_id: i32,
75    /// 话数标签
76    pub manga_episode: Option<String>,
77    /// 话数名
78    pub manga_episode_name: Option<String>,
79    /// 话数分类
80    pub episode_type: EpisodeType,
81    /// 图源 ID
82    pub provider_id: Option<i32>,
83    /// 翻译 ID
84    pub translator_id: Option<i32>,
85    /// 校对 ID
86    pub proofreader_id: Option<i32>,
87    /// 嵌字 ID
88    pub letterer_id: Option<i32>,
89    /// 时轴 ID
90    pub timer_id: Option<i32>,
91    /// 审稿 ID
92    pub reviewer_id: Option<i32>,
93    /// 设立时间
94    pub setup_time: Option<DateTime<Utc>>,
95    /// 更新时间
96    pub update_time: Option<DateTime<Utc>>,
97    /// 翻译文件
98    pub translator_file: Option<String>,
99    /// 校对文件
100    pub proofreader_file: Option<String>,
101    /// 时轴文件
102    pub timer_file: Option<String>,
103    /// 发布链接
104    pub publish_link: Option<String>,
105    /// 图源 OSS ID
106    pub provider_file_oss_id: Option<i32>,
107    /// 编辑器图源页面数量
108    pub editor_page_count: i64,
109    /// 翻译稿 OSS ID
110    pub translator_file_oss_id: Option<i32>,
111    /// 校对稿 OSS ID
112    pub proofreader_file_oss_id: Option<i32>,
113    /// 嵌字稿 OSS ID
114    pub letterer_file_oss_id: Option<i32>,
115    /// 时轴 OSS ID
116    pub timer_file_oss_id: Option<i32>,
117    /// 翻译接稿时间
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub translator_setup_time: Option<DateTime<Utc>>,
120    /// 翻译交稿时间
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub translator_update_time: Option<DateTime<Utc>>,
123    /// 校对接稿时间
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub proofreader_setup_time: Option<DateTime<Utc>>,
126    /// 校对交稿时间
127    #[serde(skip_serializing_if = "Option::is_none")]
128    pub proofreader_update_time: Option<DateTime<Utc>>,
129    /// 嵌字接稿时间
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub letterer_setup_time: Option<DateTime<Utc>>,
132    /// 嵌字交稿时间
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub letterer_update_time: Option<DateTime<Utc>>,
135    /// 时轴接稿时间
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub timer_setup_time: Option<DateTime<Utc>>,
138    /// 时轴交稿时间
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub timer_update_time: Option<DateTime<Utc>>,
141    /// 审稿接稿时间
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub reviewer_setup_time: Option<DateTime<Utc>>,
144    /// 审稿交稿时间
145    #[serde(skip_serializing_if = "Option::is_none")]
146    pub reviewer_update_time: Option<DateTime<Utc>>,
147    /// 图源组员名
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub provider_name: Option<String>,
150    /// 翻译组员名
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub translator_name: Option<String>,
153    /// 校对组员名
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub proofreader_name: Option<String>,
156    /// 嵌字组员名
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub letterer_name: Option<String>,
159    /// 时轴组员名
160    #[serde(skip_serializing_if = "Option::is_none")]
161    pub timer_name: Option<String>,
162    /// 审稿组员名
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub reviewer_name: Option<String>,
165    /// 图源文件名
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub provider_filename: Option<String>,
168    /// 翻译稿文件名
169    #[serde(skip_serializing_if = "Option::is_none")]
170    pub translator_filename: Option<String>,
171    /// 校对稿文件名
172    #[serde(skip_serializing_if = "Option::is_none")]
173    pub proofreader_filename: Option<String>,
174    /// 嵌字稿文件名
175    #[serde(skip_serializing_if = "Option::is_none")]
176    pub letterer_filename: Option<String>,
177    /// 时轴文件名
178    #[serde(skip_serializing_if = "Option::is_none")]
179    pub timer_filename: Option<String>,
180    /// 翻译共同接稿人列表
181    #[serde(default)]
182    pub translator_co_takers: Vec<RoleTakerVo>,
183    /// 校对共同接稿人列表
184    #[serde(default)]
185    pub proofreader_co_takers: Vec<RoleTakerVo>,
186}
187
188/// 话级共同接稿人展示信息
189#[derive(Debug, Clone, Serialize, Deserialize, Default)]
190#[serde(rename_all = "camelCase")]
191pub struct RoleTakerVo {
192    /// 组员 ID
193    pub member_id: i32,
194    /// 组员昵称
195    pub member_name: Option<String>,
196    /// 组员头像对象键或完整 URL
197    pub avatar_url: Option<String>,
198    /// 共同接稿时间
199    pub setup_time: Option<DateTime<Utc>>,
200    /// 该共同接稿人独立交稿时间
201    pub update_time: Option<DateTime<Utc>>,
202    /// 该共同接稿人独立稿件 OSS ID
203    pub file_oss_id: Option<i32>,
204    /// 该共同接稿人独立稿件文件名
205    pub filename: Option<String>,
206}
207
208/// 话数详情 VO
209#[derive(Debug, Clone, Serialize, Deserialize, Default)]
210#[serde(rename_all = "camelCase")]
211pub struct EpisodeDetailVo {
212    /// 话数 ID
213    pub id: i32,
214    /// 漫画 ID
215    pub manga_id: i32,
216    /// 话数标签
217    pub manga_episode: Option<String>,
218    /// 话数名
219    pub manga_episode_name: Option<String>,
220    /// 话数分类
221    pub episode_type: EpisodeType,
222    /// 图源 ID
223    pub provider_id: Option<i32>,
224    /// 翻译 ID
225    pub translator_id: Option<i32>,
226    /// 校对 ID
227    pub proofreader_id: Option<i32>,
228    /// 嵌字 ID
229    pub letterer_id: Option<i32>,
230    /// 时轴 ID
231    pub timer_id: Option<i32>,
232    /// 审稿 ID
233    pub reviewer_id: Option<i32>,
234    /// 设立时间
235    pub setup_time: Option<DateTime<Utc>>,
236    /// 更新时间
237    pub update_time: Option<DateTime<Utc>>,
238    /// 发布链接
239    pub publish_link: Option<String>,
240}
241
242/// 最新话 VO
243#[derive(Debug, Clone, Serialize, Deserialize, Default)]
244#[serde(rename_all = "camelCase")]
245pub struct NewestEpisodeVo {
246    /// 话数标签
247    pub manga_episode: Option<String>,
248    /// 翻译 ID
249    pub translator_id: Option<i32>,
250    /// 校对 ID
251    pub proofreader_id: Option<i32>,
252    /// 嵌字 ID
253    pub letterer_id: Option<i32>,
254    /// 审稿 ID
255    pub reviewer_id: Option<i32>,
256}
257
258/// 话数编辑 DTO (Episode Edit Data Transfer Object)
259///
260/// 用于创建/批量创建/更新话数。
261/// - `manga_episode_end`: 批量创建时的结束话数标签
262/// - 各岗位 ID 字段若为 None,表示不修改该岗位分配
263///
264/// 对应 Java `EpisodeEditDTO`。
265#[derive(Debug, Clone, Deserialize, Serialize)]
266#[serde(rename_all = "camelCase")]
267pub struct EpisodeEditDto {
268    /// 话数 ID
269    #[serde(
270        default,
271        deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose"
272    )]
273    pub id: Option<i32>,
274    /// 漫画 ID
275    #[serde(
276        default,
277        deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose"
278    )]
279    pub manga_id: Option<i32>,
280    /// 话数标签
281    pub manga_episode: Option<String>,
282    /// 批量结束话数
283    pub manga_episode_end: Option<String>,
284    /// 话数名
285    pub manga_episode_name: Option<String>,
286    /// 话数分类;新增缺省为 MAIN,更新缺省时保持原值。
287    pub episode_type: Option<EpisodeType>,
288    /// 图源 ID
289    #[serde(
290        default,
291        deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose"
292    )]
293    pub provider_id: Option<i32>,
294    /// 翻译 ID
295    #[serde(
296        default,
297        deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose"
298    )]
299    pub translator_id: Option<i32>,
300    /// 校对 ID
301    #[serde(
302        default,
303        deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose"
304    )]
305    pub proofreader_id: Option<i32>,
306    /// 嵌字 ID
307    #[serde(
308        default,
309        deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose"
310    )]
311    pub letterer_id: Option<i32>,
312    /// 时轴 ID
313    #[serde(
314        default,
315        deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose"
316    )]
317    pub timer_id: Option<i32>,
318    /// 审稿 ID
319    #[serde(
320        default,
321        deserialize_with = "crate::utils::query_deserialize::de_opt_i32_loose"
322    )]
323    pub reviewer_id: Option<i32>,
324    /// 发布链接
325    pub publish_link: Option<String>,
326}
327
328/// 发布链接请求
329#[derive(Debug, Clone, Deserialize, Serialize)]
330#[serde(rename_all = "camelCase")]
331pub struct PublishLinkRequest {
332    /// 话数 ID
333    pub id: i32,
334    /// 发布链接
335    pub publish_link: Option<String>,
336}
337
338/// 上传页 VO
339#[derive(Debug, Clone, Serialize, Deserialize, Default)]
340#[serde(rename_all = "camelCase")]
341pub struct UploadPageVo {
342    /// 话数 ID
343    pub id: i32,
344    /// 漫画 ID
345    pub manga_id: i32,
346    /// 漫画译名
347    pub manga_name: Option<String>,
348    /// 话数标签
349    pub manga_episode: Option<String>,
350    /// 更新时间
351    pub update_time: Option<DateTime<Utc>>,
352    /// 翻译文件
353    pub translator_file: Option<String>,
354    /// 校对文件
355    pub proofreader_file: Option<String>,
356    /// 时轴文件
357    pub timer_file: Option<String>,
358    /// 图源 ID
359    pub provider_id: Option<i32>,
360    /// 翻译 ID
361    pub translator_id: Option<i32>,
362    /// 校对 ID
363    pub proofreader_id: Option<i32>,
364    /// 嵌字 ID
365    pub letterer_id: Option<i32>,
366    /// 时轴 ID
367    pub timer_id: Option<i32>,
368    /// 审稿 ID
369    pub reviewer_id: Option<i32>,
370    /// 图源组员名
371    #[serde(skip_serializing_if = "Option::is_none")]
372    pub provider_name: Option<String>,
373    /// 翻译组员名
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub translator_name: Option<String>,
376    /// 校对组员名
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub proofreader_name: Option<String>,
379    /// 嵌字组员名
380    #[serde(skip_serializing_if = "Option::is_none")]
381    pub letterer_name: Option<String>,
382    /// 时轴组员名
383    #[serde(skip_serializing_if = "Option::is_none")]
384    pub timer_name: Option<String>,
385    /// 审稿组员名
386    #[serde(skip_serializing_if = "Option::is_none")]
387    pub reviewer_name: Option<String>,
388    /// 图源 OSS ID
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub provider_file_oss_id: Option<i32>,
391    /// 翻译稿 OSS ID
392    #[serde(skip_serializing_if = "Option::is_none")]
393    pub translator_file_oss_id: Option<i32>,
394    /// 校对稿 OSS ID
395    #[serde(skip_serializing_if = "Option::is_none")]
396    pub proofreader_file_oss_id: Option<i32>,
397    /// 嵌字稿 OSS ID
398    #[serde(skip_serializing_if = "Option::is_none")]
399    pub letterer_file_oss_id: Option<i32>,
400    /// 时轴 OSS ID
401    #[serde(skip_serializing_if = "Option::is_none")]
402    pub timer_file_oss_id: Option<i32>,
403    /// 图源文件名
404    #[serde(skip_serializing_if = "Option::is_none")]
405    pub provider_filename: Option<String>,
406    /// 翻译稿文件名
407    #[serde(skip_serializing_if = "Option::is_none")]
408    pub translator_filename: Option<String>,
409    /// 校对稿文件名
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub proofreader_filename: Option<String>,
412    /// 嵌字稿文件名
413    #[serde(skip_serializing_if = "Option::is_none")]
414    pub letterer_filename: Option<String>,
415    /// 时轴文件名
416    #[serde(skip_serializing_if = "Option::is_none")]
417    pub timer_filename: Option<String>,
418    /// 翻译邮箱
419    #[serde(skip_serializing_if = "Option::is_none")]
420    pub email: Option<String>,
421    /// 当前岗位邮箱
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub email_now: Option<String>,
424}
425
426/// 统计(对齐 Java Statistics)
427#[derive(Debug, Clone, Serialize, Deserialize, Default)]
428#[serde(rename_all = "camelCase")]
429pub struct Statistics {
430    /// 翻译完成数
431    #[serde(default)]
432    pub translator_count: i32,
433    /// 校对完成数
434    #[serde(default)]
435    pub proofreader_count: i32,
436    /// 嵌字完成数
437    #[serde(default)]
438    pub letterer_count: i32,
439    /// 审稿完成数
440    #[serde(default)]
441    pub reviewer_count: i32,
442}
443
444/// 组员统计(对齐 Java MemberStatistics)
445#[derive(Debug, Clone, Serialize, Deserialize, Default)]
446#[serde(rename_all = "camelCase")]
447pub struct MemberStatistics {
448    /// 组员用户名
449    pub user_name: Option<String>,
450    /// 组员 ID
451    pub user_id: Option<String>,
452    /// 翻译完成数
453    pub translator_count: i32,
454    /// 校对完成数
455    pub proofreader_count: i32,
456    /// 嵌字完成数
457    pub letterer_count: i32,
458    /// 时轴完成数
459    pub timer_count: i32,
460    /// 审稿完成数
461    pub reviewer_count: i32,
462}
463
464/// 稿件任务(对齐 Java EpisodeTask)
465#[derive(Debug, Clone, Serialize, Deserialize, Default)]
466#[serde(rename_all = "camelCase")]
467pub struct EpisodeTask {
468    /// 漫画 ID
469    pub manga_id: Option<i32>,
470    /// 话数 ID
471    pub episode_id: Option<i32>,
472    /// 话数标签
473    pub manga_episode: Option<String>,
474    /// 话数名
475    pub episode_name: Option<String>,
476    /// 负责组员 ID
477    pub member_id: Option<i32>,
478    /// 接稿时间
479    pub setup_time: Option<DateTime<Utc>>,
480    /// 前一岗位交稿时间
481    pub previous_update_time: Option<DateTime<Utc>>,
482    /// 任务类型(PostEnum 名)
483    pub post: Option<String>,
484}
485
486/// 任务追踪(对齐 Java MemberTaskCount)
487#[derive(Debug, Clone, Serialize, Deserialize, Default)]
488#[serde(rename_all = "camelCase")]
489pub struct MemberTaskCount {
490    /// 组员用户名
491    pub user_name: Option<String>,
492    /// 组员 ID
493    pub user_id: Option<String>,
494    /// 职阶
495    pub intern: Option<i32>,
496    /// 邮箱
497    pub email: Option<String>,
498    /// 最后交稿时间
499    pub last_update_time: Option<String>,
500    /// 常驻数
501    pub stationed_count: i32,
502    /// 翻译任务数
503    pub translator_count: i32,
504    /// 校对任务数
505    pub proofreader_count: i32,
506    /// 嵌字任务数
507    pub letterer_count: i32,
508    /// 时轴任务数
509    pub timer_count: i32,
510    /// 审稿任务数
511    pub reviewer_count: i32,
512}
513
514impl MemberTaskCount {
515    /// 总任务数
516    pub fn total_count(&self) -> i32 {
517        self.translator_count
518            + self.proofreader_count
519            + self.letterer_count
520            + self.timer_count
521            + self.reviewer_count
522    }
523}
524
525/// 任务追踪响应 (Task Tracking Response)
526///
527/// 按岗位分组的话数任务列表,用于组员任务看板。
528///
529/// 对应 Java `TaskTrackingResponse`。
530#[derive(Debug, Clone, Serialize, Deserialize, Default)]
531#[serde(rename_all = "camelCase")]
532pub struct TaskTrackingResponse {
533    /// 翻译稿件列表
534    pub translator_task_list: Vec<EpisodeTask>,
535    /// 校对稿件列表
536    pub proofreader_task_list: Vec<EpisodeTask>,
537    /// 嵌字稿件列表
538    pub letterer_task_list: Vec<EpisodeTask>,
539    /// 时轴稿件列表
540    pub timer_task_list: Vec<EpisodeTask>,
541    /// 审稿稿件列表
542    pub reviewer_task_list: Vec<EpisodeTask>,
543    /// 发布稿件列表
544    pub publish_task_list: Vec<EpisodeTask>,
545    /// 全部稿件列表
546    pub all_task_list: Vec<EpisodeTask>,
547}
548
549impl TaskTrackingResponse {
550    /// 空列表初始化
551    pub fn empty() -> Self {
552        Self::default()
553    }
554}
555
556/// 工序汇总筛选结果项(稿件监控 workflowEpisodes 接口)
557#[derive(Debug, Clone, Serialize, Deserialize, Default)]
558#[serde(rename_all = "camelCase")]
559pub struct WorkflowEpisodeItem {
560    /// 漫画 ID
561    pub manga_id: i32,
562    /// 话数 ID
563    pub episode_id: i32,
564    /// 话数标签
565    pub manga_episode: Option<String>,
566    /// 话数名
567    pub episode_name: Option<String>,
568    /// 漫画译名
569    pub manga_tran_name: Option<String>,
570    /// 漫画分类
571    pub category: Option<i16>,
572    /// 当前环节负责人 ID
573    pub member_id: Option<i32>,
574    /// 当前环节负责人昵称
575    pub member_name: Option<String>,
576    /// 环节展示标签(翻/嵌/轴/审/发/填链接)
577    pub step_label: Option<String>,
578    /// 匹配环节(translator / proofreader / letterer / reviewer / publishLink 等)
579    pub match_step: Option<String>,
580    /// 组员职阶
581    pub intern: Option<i32>,
582    /// 前一环节交稿时间
583    pub previous_update_time: Option<DateTime<Utc>>,
584    /// 当前环节接稿时间
585    pub setup_time: Option<DateTime<Utc>>,
586}
587
588/// 话数 POJO(Member 模块用)
589#[derive(Debug, Clone, Serialize, Deserialize)]
590#[serde(rename_all = "camelCase")]
591pub struct MangaEpisode {
592    /// 话数 ID
593    #[serde(rename = "Id")]
594    pub id: i32,
595    /// 漫画 ID
596    pub manga_id: i32,
597    /// 话数标签
598    pub manga_episode: Option<String>,
599    /// 漫画名
600    pub manga_name: Option<String>,
601    /// 分类
602    pub category: Option<i16>,
603}
604
605/// 待发布漫画任务 (Pending Manga Task)
606///
607/// 用于 RSS 定时任务扫描:查找有已完成审稿但尚未发布话数的漫画。
608///
609/// - `mangatb`: 漫画基本信息
610/// - `newest_manga_episode`: 最新已发布话数标签
611/// - `pending_episode_list`: 审稿完成待发布的话数列表
612///
613/// 对应 Java `PendingMangaTask`。
614#[derive(Debug, Clone, Serialize, Deserialize)]
615#[serde(rename_all = "camelCase")]
616pub struct PendingMangaTask {
617    /// 漫画信息
618    pub mangatb: crate::entity::manga::Mangatb,
619    /// 最新已发布话数标签
620    pub newest_manga_episode: Option<String>,
621    /// 待处理话数列表
622    pub pending_episode_list: Vec<PendingEpisode>,
623}
624/// 话数数据库行 (Manga Episode Table Row)
625///
626/// 映射 `t_manga_episode` 表,用于仓储层行映射和 [`PendingEpisode`]。
627#[derive(Debug, Clone, Serialize, Deserialize, Default)]
628#[serde(rename_all = "camelCase")]
629pub struct MangaEpisodeTb {
630    /// 话数 ID
631    pub id: i32,
632    /// 漫画 ID
633    pub manga_id: i32,
634    /// 话数标签
635    pub manga_episode: Option<String>,
636    /// 话数名
637    pub manga_episode_name: Option<String>,
638    /// 话数分类
639    pub episode_type: EpisodeType,
640    /// 图源 ID
641    pub provider_id: Option<i32>,
642    /// 翻译 ID
643    pub translator_id: Option<i32>,
644    /// 校对 ID
645    pub proofreader_id: Option<i32>,
646    /// 嵌字 ID
647    pub letterer_id: Option<i32>,
648    /// 时轴 ID
649    pub timer_id: Option<i32>,
650    /// 审稿 ID
651    pub reviewer_id: Option<i32>,
652    /// 设立时间
653    pub setup_time: Option<DateTime<Utc>>,
654    /// 更新时间
655    pub update_time: Option<DateTime<Utc>>,
656    /// 翻译文件
657    pub translator_file: Option<String>,
658    /// 校对文件
659    pub proofreader_file: Option<String>,
660    /// 时轴文件
661    pub timer_file: Option<String>,
662    /// 发布链接
663    pub publish_link: Option<String>,
664    /// 图源 OSS ID
665    pub provider_file_oss_id: Option<i32>,
666    /// 翻译稿 OSS ID
667    pub translator_file_oss_id: Option<i32>,
668    /// 校对稿 OSS ID
669    pub proofreader_file_oss_id: Option<i32>,
670    /// 嵌字稿 OSS ID
671    pub letterer_file_oss_id: Option<i32>,
672    /// 时轴 OSS ID
673    pub timer_file_oss_id: Option<i32>,
674}
675
676/// 待发布单话(对齐 Java PendingEpisode)
677#[derive(Debug, Clone, Serialize, Deserialize)]
678#[serde(rename_all = "camelCase")]
679pub struct PendingEpisode {
680    /// 单话信息
681    pub mangaepisodetb: MangaEpisodeTb,
682    /// 当前状态(reviewer / publisher)
683    pub status: String,
684    /// 是否下一个待发布话
685    pub next_publish: bool,
686}
687
688#[cfg(test)]
689mod episode_type_tests {
690    use super::EpisodeType;
691
692    #[test]
693    fn episode_type_uses_api_contract_values() {
694        assert_eq!(serde_json::to_string(&EpisodeType::Main).unwrap(), "\"MAIN\"");
695        assert_eq!(serde_json::to_string(&EpisodeType::Extra).unwrap(), "\"EXTRA\"");
696        assert_eq!(
697            serde_json::from_str::<EpisodeType>("\"EXTRA\"").unwrap(),
698            EpisodeType::Extra
699        );
700    }
701}