feat(message): add message center module (Phase 5)
Implement the complete message center with: - Database migrations for message_templates, messages, message_subscriptions tables - erp-message crate with entities, DTOs, services, handlers - Message CRUD, send, read/unread tracking, soft delete - Template management with variable interpolation - Subscription preferences with DND support - Frontend: messages page, notification panel, unread count badge - Server integration with module registration and routing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
144
crates/erp-message/src/dto.rs
Normal file
144
crates/erp-message/src/dto.rs
Normal file
@@ -0,0 +1,144 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
use utoipa::ToSchema;
|
||||
use validator::Validate;
|
||||
|
||||
// ============ 消息 DTO ============
|
||||
|
||||
/// 消息响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct MessageResp {
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub template_id: Option<Uuid>,
|
||||
pub sender_id: Option<Uuid>,
|
||||
pub sender_type: String,
|
||||
pub recipient_id: Uuid,
|
||||
pub recipient_type: String,
|
||||
pub title: String,
|
||||
pub body: String,
|
||||
pub priority: String,
|
||||
pub business_type: Option<String>,
|
||||
pub business_id: Option<Uuid>,
|
||||
pub is_read: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub read_at: Option<DateTime<Utc>>,
|
||||
pub is_archived: bool,
|
||||
pub status: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub sent_at: Option<DateTime<Utc>>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 发送消息请求
|
||||
#[derive(Debug, Deserialize, Validate, ToSchema)]
|
||||
pub struct SendMessageReq {
|
||||
#[validate(length(min = 1, max = 200, message = "标题不能为空且不超过200字符"))]
|
||||
pub title: String,
|
||||
#[validate(length(min = 1, message = "内容不能为空"))]
|
||||
pub body: String,
|
||||
pub recipient_id: Uuid,
|
||||
#[serde(default = "default_recipient_type")]
|
||||
pub recipient_type: String,
|
||||
#[serde(default = "default_priority")]
|
||||
pub priority: String,
|
||||
pub template_id: Option<Uuid>,
|
||||
pub business_type: Option<String>,
|
||||
pub business_id: Option<Uuid>,
|
||||
}
|
||||
|
||||
fn default_recipient_type() -> String {
|
||||
"user".to_string()
|
||||
}
|
||||
|
||||
fn default_priority() -> String {
|
||||
"normal".to_string()
|
||||
}
|
||||
|
||||
/// 消息列表查询参数
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct MessageQuery {
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
pub is_read: Option<bool>,
|
||||
pub priority: Option<String>,
|
||||
pub business_type: Option<String>,
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
/// 未读消息计数响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct UnreadCountResp {
|
||||
pub count: i64,
|
||||
}
|
||||
|
||||
// ============ 消息模板 DTO ============
|
||||
|
||||
/// 消息模板响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct MessageTemplateResp {
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub name: String,
|
||||
pub code: String,
|
||||
pub channel: String,
|
||||
pub title_template: String,
|
||||
pub body_template: String,
|
||||
pub language: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 创建消息模板请求
|
||||
#[derive(Debug, Deserialize, Validate, ToSchema)]
|
||||
pub struct CreateTemplateReq {
|
||||
#[validate(length(min = 1, max = 100, message = "名称不能为空且不超过100字符"))]
|
||||
pub name: String,
|
||||
#[validate(length(min = 1, max = 50, message = "编码不能为空且不超过50字符"))]
|
||||
pub code: String,
|
||||
#[serde(default = "default_channel")]
|
||||
pub channel: String,
|
||||
#[validate(length(min = 1, max = 200, message = "标题模板不能为空"))]
|
||||
pub title_template: String,
|
||||
#[validate(length(min = 1, message = "内容模板不能为空"))]
|
||||
pub body_template: String,
|
||||
#[serde(default = "default_language")]
|
||||
pub language: String,
|
||||
}
|
||||
|
||||
fn default_channel() -> String {
|
||||
"in_app".to_string()
|
||||
}
|
||||
|
||||
fn default_language() -> String {
|
||||
"zh-CN".to_string()
|
||||
}
|
||||
|
||||
// ============ 消息订阅偏好 DTO ============
|
||||
|
||||
/// 消息订阅偏好响应
|
||||
#[derive(Debug, Serialize, ToSchema)]
|
||||
pub struct MessageSubscriptionResp {
|
||||
pub id: Uuid,
|
||||
pub tenant_id: Uuid,
|
||||
pub user_id: Uuid,
|
||||
pub notification_types: Option<serde_json::Value>,
|
||||
pub channel_preferences: Option<serde_json::Value>,
|
||||
pub dnd_enabled: bool,
|
||||
pub dnd_start: Option<String>,
|
||||
pub dnd_end: Option<String>,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub updated_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
/// 更新消息订阅偏好请求
|
||||
#[derive(Debug, Deserialize, ToSchema)]
|
||||
pub struct UpdateSubscriptionReq {
|
||||
pub notification_types: Option<serde_json::Value>,
|
||||
pub channel_preferences: Option<serde_json::Value>,
|
||||
pub dnd_enabled: Option<bool>,
|
||||
pub dnd_start: Option<String>,
|
||||
pub dnd_end: Option<String>,
|
||||
}
|
||||
Reference in New Issue
Block a user