Add VersionMismatch error variant and check_version() helper to erp-core. All 13 mutable entities now enforce version checking on update/delete: - erp-auth: user, role, organization, department, position - erp-config: dictionary, dictionary_item, menu, setting, numbering_rule - erp-workflow: process_definition, process_instance, task - erp-message: message, message_subscription Update DTOs to expose version in responses and require version in update requests. HTTP 409 Conflict returned on version mismatch.
124 lines
4.6 KiB
Rust
124 lines
4.6 KiB
Rust
use chrono::Utc;
|
||
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, Set};
|
||
use uuid::Uuid;
|
||
|
||
use crate::dto::{MessageSubscriptionResp, UpdateSubscriptionReq};
|
||
use crate::entity::message_subscription;
|
||
use crate::error::{MessageError, MessageResult};
|
||
use erp_core::error::check_version;
|
||
|
||
/// 消息订阅偏好服务。
|
||
pub struct SubscriptionService;
|
||
|
||
impl SubscriptionService {
|
||
/// 获取用户订阅偏好。
|
||
pub async fn get(
|
||
tenant_id: Uuid,
|
||
user_id: Uuid,
|
||
db: &sea_orm::DatabaseConnection,
|
||
) -> MessageResult<MessageSubscriptionResp> {
|
||
let model = message_subscription::Entity::find()
|
||
.filter(message_subscription::Column::TenantId.eq(tenant_id))
|
||
.filter(message_subscription::Column::UserId.eq(user_id))
|
||
.filter(message_subscription::Column::DeletedAt.is_null())
|
||
.one(db)
|
||
.await
|
||
.map_err(|e| MessageError::Validation(e.to_string()))?
|
||
.ok_or_else(|| MessageError::NotFound("订阅偏好不存在".to_string()))?;
|
||
|
||
Ok(Self::model_to_resp(&model))
|
||
}
|
||
|
||
/// 创建或更新用户订阅偏好(upsert)。
|
||
pub async fn upsert(
|
||
tenant_id: Uuid,
|
||
user_id: Uuid,
|
||
req: &UpdateSubscriptionReq,
|
||
db: &sea_orm::DatabaseConnection,
|
||
) -> MessageResult<MessageSubscriptionResp> {
|
||
let existing = message_subscription::Entity::find()
|
||
.filter(message_subscription::Column::TenantId.eq(tenant_id))
|
||
.filter(message_subscription::Column::UserId.eq(user_id))
|
||
.filter(message_subscription::Column::DeletedAt.is_null())
|
||
.one(db)
|
||
.await
|
||
.map_err(|e| MessageError::Validation(e.to_string()))?;
|
||
|
||
let now = Utc::now();
|
||
|
||
if let Some(model) = existing {
|
||
let current_version = model.version;
|
||
let next_ver = check_version(req.version, current_version)
|
||
.map_err(|_| MessageError::VersionMismatch)?;
|
||
let mut active: message_subscription::ActiveModel = model.into();
|
||
if let Some(types) = &req.notification_types {
|
||
active.notification_types = Set(Some(types.clone()));
|
||
}
|
||
if let Some(prefs) = &req.channel_preferences {
|
||
active.channel_preferences = Set(Some(prefs.clone()));
|
||
}
|
||
if let Some(dnd) = req.dnd_enabled {
|
||
active.dnd_enabled = Set(dnd);
|
||
}
|
||
if let Some(ref start) = req.dnd_start {
|
||
active.dnd_start = Set(Some(start.clone()));
|
||
}
|
||
if let Some(ref end) = req.dnd_end {
|
||
active.dnd_end = Set(Some(end.clone()));
|
||
}
|
||
active.updated_at = Set(now);
|
||
active.updated_by = Set(user_id);
|
||
active.version = Set(next_ver);
|
||
|
||
let updated = active
|
||
.update(db)
|
||
.await
|
||
.map_err(|e| MessageError::Validation(e.to_string()))?;
|
||
|
||
Ok(Self::model_to_resp(&updated))
|
||
} else {
|
||
let id = Uuid::now_v7();
|
||
|
||
let model = message_subscription::ActiveModel {
|
||
id: Set(id),
|
||
tenant_id: Set(tenant_id),
|
||
user_id: Set(user_id),
|
||
notification_types: Set(req.notification_types.clone()),
|
||
channel_preferences: Set(req.channel_preferences.clone()),
|
||
dnd_enabled: Set(req.dnd_enabled.unwrap_or(false)),
|
||
dnd_start: Set(req.dnd_start.clone()),
|
||
dnd_end: Set(req.dnd_end.clone()),
|
||
created_at: Set(now),
|
||
updated_at: Set(now),
|
||
created_by: Set(user_id),
|
||
updated_by: Set(user_id),
|
||
deleted_at: Set(None),
|
||
version: Set(1),
|
||
};
|
||
|
||
let inserted = model
|
||
.insert(db)
|
||
.await
|
||
.map_err(|e| MessageError::Validation(e.to_string()))?;
|
||
|
||
Ok(Self::model_to_resp(&inserted))
|
||
}
|
||
}
|
||
|
||
fn model_to_resp(m: &message_subscription::Model) -> MessageSubscriptionResp {
|
||
MessageSubscriptionResp {
|
||
id: m.id,
|
||
tenant_id: m.tenant_id,
|
||
user_id: m.user_id,
|
||
notification_types: m.notification_types.clone(),
|
||
channel_preferences: m.channel_preferences.clone(),
|
||
dnd_enabled: m.dnd_enabled,
|
||
dnd_start: m.dnd_start.clone(),
|
||
dnd_end: m.dnd_end.clone(),
|
||
created_at: m.created_at,
|
||
updated_at: m.updated_at,
|
||
version: m.version,
|
||
}
|
||
}
|
||
}
|