- 新建 erp-points crate(8 Entity + account/product service + handler) - 商品 CRUD 和账户管理完整实现,订单/签到/规则端点暂返回 501 - 注册到 workspace + erp-server 路由 /api/v1/points/* - API 路径不变,前端无需修改
315 lines
9.3 KiB
Rust
315 lines
9.3 KiB
Rust
use chrono::NaiveDate;
|
|
use erp_core::sanitize::sanitize_option;
|
|
use serde::{Deserialize, Serialize};
|
|
use utoipa::ToSchema;
|
|
use uuid::Uuid;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 积分账户
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct PointsAccountResp {
|
|
pub id: Uuid,
|
|
pub patient_id: Uuid,
|
|
pub balance: i32,
|
|
pub total_earned: i32,
|
|
pub total_spent: i32,
|
|
pub total_expired: i32,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
|
pub version: i32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct CreateAccountReq {
|
|
pub patient_id: Uuid,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct CheckinStatusResp {
|
|
pub checked_in_today: bool,
|
|
pub consecutive_days: i32,
|
|
pub next_streak_milestone: Option<i32>,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 积分流水
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct PointsTransactionResp {
|
|
pub id: Uuid,
|
|
pub account_id: Uuid,
|
|
pub transaction_type: String,
|
|
pub amount: i32,
|
|
pub remaining_amount: i32,
|
|
pub status: String,
|
|
pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
pub balance_after: i32,
|
|
pub description: Option<String>,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 积分规则
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct CreatePointsRuleReq {
|
|
pub event_type: String,
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub points_value: i32,
|
|
#[serde(default)]
|
|
pub daily_cap: i32,
|
|
#[serde(default)]
|
|
pub streak_7d_bonus: i32,
|
|
#[serde(default)]
|
|
pub streak_14d_bonus: i32,
|
|
#[serde(default)]
|
|
pub streak_30d_bonus: i32,
|
|
}
|
|
|
|
impl CreatePointsRuleReq {
|
|
pub fn sanitize(&mut self) {
|
|
self.description = sanitize_option(self.description.take());
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct UpdatePointsRuleReq {
|
|
pub name: Option<String>,
|
|
pub description: Option<String>,
|
|
pub points_value: Option<i32>,
|
|
pub daily_cap: Option<i32>,
|
|
pub streak_7d_bonus: Option<i32>,
|
|
pub streak_14d_bonus: Option<i32>,
|
|
pub streak_30d_bonus: Option<i32>,
|
|
pub is_active: Option<bool>,
|
|
}
|
|
|
|
impl UpdatePointsRuleReq {
|
|
pub fn sanitize(&mut self) {
|
|
self.description = sanitize_option(self.description.take());
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct PointsRuleResp {
|
|
pub id: Uuid,
|
|
pub event_type: String,
|
|
pub name: String,
|
|
pub description: Option<String>,
|
|
pub points_value: i32,
|
|
pub daily_cap: i32,
|
|
pub streak_7d_bonus: i32,
|
|
pub streak_14d_bonus: i32,
|
|
pub streak_30d_bonus: i32,
|
|
pub is_active: bool,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
|
pub version: i32,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 兑换商品
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct CreatePointsProductReq {
|
|
pub name: String,
|
|
pub product_type: Option<String>,
|
|
pub points_cost: i32,
|
|
pub stock: Option<i32>,
|
|
pub image_url: Option<String>,
|
|
pub description: Option<String>,
|
|
pub service_config: Option<serde_json::Value>,
|
|
pub sort_order: Option<i32>,
|
|
}
|
|
|
|
impl CreatePointsProductReq {
|
|
pub fn sanitize(&mut self) {
|
|
self.description = sanitize_option(self.description.take());
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct UpdatePointsProductReq {
|
|
pub name: Option<String>,
|
|
pub product_type: Option<String>,
|
|
pub points_cost: Option<i32>,
|
|
pub stock: Option<i32>,
|
|
pub image_url: Option<String>,
|
|
pub description: Option<String>,
|
|
pub service_config: Option<serde_json::Value>,
|
|
pub is_active: Option<bool>,
|
|
pub sort_order: Option<i32>,
|
|
}
|
|
|
|
impl UpdatePointsProductReq {
|
|
pub fn sanitize(&mut self) {
|
|
self.description = sanitize_option(self.description.take());
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct PointsProductResp {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
pub product_type: String,
|
|
pub points_cost: i32,
|
|
pub stock: i32,
|
|
pub image_url: Option<String>,
|
|
pub description: Option<String>,
|
|
pub is_active: bool,
|
|
pub sort_order: i32,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
|
pub version: i32,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 兑换订单
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct CreateOrderReq {
|
|
pub product_id: Uuid,
|
|
pub patient_id: Uuid,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct PointsOrderResp {
|
|
pub id: Uuid,
|
|
pub patient_id: Uuid,
|
|
pub product_id: Uuid,
|
|
pub product_name: Option<String>,
|
|
pub points_cost: i32,
|
|
pub status: String,
|
|
pub qr_code: Option<Uuid>,
|
|
pub verified_by: Option<Uuid>,
|
|
pub verified_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
|
|
pub notes: Option<String>,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
|
pub version: i32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct VerifyOrderReq {
|
|
pub qr_code: Uuid,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 线下活动
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct CreateOfflineEventReq {
|
|
pub title: String,
|
|
pub description: Option<String>,
|
|
pub event_date: NaiveDate,
|
|
pub start_time: Option<chrono::NaiveTime>,
|
|
pub end_time: Option<chrono::NaiveTime>,
|
|
pub location: Option<String>,
|
|
pub points_reward: Option<i32>,
|
|
pub max_participants: Option<i32>,
|
|
pub image_url: Option<String>,
|
|
}
|
|
|
|
impl CreateOfflineEventReq {
|
|
pub fn sanitize(&mut self) {
|
|
self.description = sanitize_option(self.description.take());
|
|
self.location = sanitize_option(self.location.take());
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct UpdateOfflineEventReq {
|
|
pub title: Option<String>,
|
|
pub description: Option<String>,
|
|
pub event_date: Option<NaiveDate>,
|
|
pub start_time: Option<chrono::NaiveTime>,
|
|
pub end_time: Option<chrono::NaiveTime>,
|
|
pub location: Option<String>,
|
|
pub points_reward: Option<i32>,
|
|
pub max_participants: Option<i32>,
|
|
pub status: Option<String>,
|
|
pub image_url: Option<String>,
|
|
}
|
|
|
|
impl UpdateOfflineEventReq {
|
|
pub fn sanitize(&mut self) {
|
|
self.description = sanitize_option(self.description.take());
|
|
self.location = sanitize_option(self.location.take());
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct OfflineEventResp {
|
|
pub id: Uuid,
|
|
pub title: String,
|
|
pub description: Option<String>,
|
|
pub event_date: NaiveDate,
|
|
pub start_time: Option<chrono::NaiveTime>,
|
|
pub end_time: Option<chrono::NaiveTime>,
|
|
pub location: Option<String>,
|
|
pub points_reward: i32,
|
|
pub max_participants: i32,
|
|
pub current_participants: i32,
|
|
pub status: String,
|
|
pub image_url: Option<String>,
|
|
pub created_at: chrono::DateTime<chrono::Utc>,
|
|
pub updated_at: chrono::DateTime<chrono::Utc>,
|
|
pub version: i32,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 管理端:带版本号的更新/删除包装
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct UpdateRuleWithVersion {
|
|
pub data: UpdatePointsRuleReq,
|
|
pub version: i32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct UpdateProductWithVersion {
|
|
pub data: UpdatePointsProductReq,
|
|
pub version: i32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct UpdateOfflineEventWithVersion {
|
|
pub data: UpdateOfflineEventReq,
|
|
pub version: i32,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct AdminCheckinReq {
|
|
pub patient_id: Uuid,
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 积分统计
|
|
// ---------------------------------------------------------------------------
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct PointsStatisticsResp {
|
|
pub total_issued: i64,
|
|
pub total_spent: i64,
|
|
pub total_expired: i64,
|
|
pub active_accounts: i64,
|
|
pub top_earners: Vec<TopEarner>,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
|
|
pub struct TopEarner {
|
|
pub account_id: Uuid,
|
|
pub patient_id: Uuid,
|
|
pub total_earned: i32,
|
|
}
|