409 lines
12 KiB
Rust
409 lines
12 KiB
Rust
//! 股息分配系统
|
||
//!
|
||
//! 实现证券型资产的股息计算、分配和记录功能
|
||
|
||
use std::collections::HashMap;
|
||
use serde::{Serialize, Deserialize};
|
||
|
||
/// 股息分配记录
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct DividendRecord {
|
||
/// 分配ID
|
||
pub id: String,
|
||
/// 证券分区ID
|
||
pub security_id: [u8; 32],
|
||
/// 分配时间戳
|
||
pub timestamp: u64,
|
||
/// 每股股息金额
|
||
pub amount_per_share: u64,
|
||
/// 总分配金额
|
||
pub total_amount: u64,
|
||
/// 受益人数量
|
||
pub beneficiary_count: usize,
|
||
/// 分配状态
|
||
pub status: DividendStatus,
|
||
/// 税率(百分比,例如15表示15%)
|
||
pub tax_rate: u8,
|
||
}
|
||
|
||
/// 股息分配状态
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
pub enum DividendStatus {
|
||
/// 待分配
|
||
Pending,
|
||
/// 分配中
|
||
Distributing,
|
||
/// 已完成
|
||
Completed,
|
||
/// 已取消
|
||
Cancelled,
|
||
}
|
||
|
||
/// 个人股息记录
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct PersonalDividend {
|
||
/// 分配ID
|
||
pub dividend_id: String,
|
||
/// 账户地址
|
||
pub account: String,
|
||
/// 持股数量
|
||
pub shares: u64,
|
||
/// 税前金额
|
||
pub gross_amount: u64,
|
||
/// 税额
|
||
pub tax_amount: u64,
|
||
/// 税后金额(实际到账)
|
||
pub net_amount: u64,
|
||
/// 领取状态
|
||
pub claimed: bool,
|
||
/// 领取时间
|
||
pub claim_time: Option<u64>,
|
||
}
|
||
|
||
/// 股息分配引擎
|
||
#[derive(Debug)]
|
||
pub struct DividendEngine {
|
||
/// 股息分配记录
|
||
records: HashMap<String, DividendRecord>,
|
||
/// 个人股息记录
|
||
personal_dividends: HashMap<String, Vec<PersonalDividend>>,
|
||
/// 下一个分配ID
|
||
next_id: u64,
|
||
}
|
||
|
||
impl DividendEngine {
|
||
/// 创建新的股息分配引擎
|
||
pub fn new() -> Self {
|
||
Self {
|
||
records: HashMap::new(),
|
||
personal_dividends: HashMap::new(),
|
||
next_id: 1,
|
||
}
|
||
}
|
||
|
||
/// 声明股息分配
|
||
///
|
||
/// # 参数
|
||
/// - security_id: 证券分区ID
|
||
/// - amount_per_share: 每股股息金额
|
||
/// - tax_rate: 税率(百分比)
|
||
/// - holders: 持有人及其持股数量
|
||
pub fn declare_dividend(
|
||
&mut self,
|
||
security_id: [u8; 32],
|
||
amount_per_share: u64,
|
||
tax_rate: u8,
|
||
holders: &HashMap<String, u64>,
|
||
) -> Result<String, String> {
|
||
// 验证参数
|
||
if amount_per_share == 0 {
|
||
return Err("Amount per share must be greater than zero".to_string());
|
||
}
|
||
|
||
if tax_rate > 100 {
|
||
return Err("Tax rate must be between 0 and 100".to_string());
|
||
}
|
||
|
||
if holders.is_empty() {
|
||
return Err("No holders specified".to_string());
|
||
}
|
||
|
||
// 生成分配ID
|
||
let dividend_id = format!("DIV-{:08}", self.next_id);
|
||
self.next_id += 1;
|
||
|
||
// 计算总金额
|
||
let total_shares: u64 = holders.values().sum();
|
||
let total_amount = total_shares * amount_per_share;
|
||
|
||
// 创建分配记录
|
||
let record = DividendRecord {
|
||
id: dividend_id.clone(),
|
||
security_id,
|
||
timestamp: Self::current_timestamp(),
|
||
amount_per_share,
|
||
total_amount,
|
||
beneficiary_count: holders.len(),
|
||
status: DividendStatus::Pending,
|
||
tax_rate,
|
||
};
|
||
|
||
self.records.insert(dividend_id.clone(), record);
|
||
|
||
// 为每个持有人创建个人股息记录
|
||
for (account, shares) in holders {
|
||
let gross_amount = shares * amount_per_share;
|
||
let tax_amount = (gross_amount * tax_rate as u64) / 100;
|
||
let net_amount = gross_amount - tax_amount;
|
||
|
||
let personal_dividend = PersonalDividend {
|
||
dividend_id: dividend_id.clone(),
|
||
account: account.clone(),
|
||
shares: *shares,
|
||
gross_amount,
|
||
tax_amount,
|
||
net_amount,
|
||
claimed: false,
|
||
claim_time: None,
|
||
};
|
||
|
||
self.personal_dividends
|
||
.entry(account.clone())
|
||
.or_insert_with(Vec::new)
|
||
.push(personal_dividend);
|
||
}
|
||
|
||
Ok(dividend_id)
|
||
}
|
||
|
||
/// 执行股息分配
|
||
pub fn distribute_dividend(&mut self, dividend_id: &str) -> Result<(), String> {
|
||
let record = self.records.get_mut(dividend_id)
|
||
.ok_or_else(|| "Dividend not found".to_string())?;
|
||
|
||
if record.status != DividendStatus::Pending {
|
||
return Err(format!("Dividend is not in pending status: {:?}", record.status));
|
||
}
|
||
|
||
// 更新状态为分配中
|
||
record.status = DividendStatus::Distributing;
|
||
|
||
// 实际分配逻辑(这里简化为标记为已分配)
|
||
// 在真实实现中,这里会调用转账功能将资金分配给持有人
|
||
|
||
// 更新状态为已完成
|
||
record.status = DividendStatus::Completed;
|
||
|
||
Ok(())
|
||
}
|
||
|
||
/// 领取股息
|
||
pub fn claim_dividend(&mut self, account: &str, dividend_id: &str) -> Result<u64, String> {
|
||
let personal_dividends = self.personal_dividends.get_mut(account)
|
||
.ok_or_else(|| "No dividends for this account".to_string())?;
|
||
|
||
let dividend = personal_dividends.iter_mut()
|
||
.find(|d| d.dividend_id == dividend_id)
|
||
.ok_or_else(|| "Dividend not found for this account".to_string())?;
|
||
|
||
if dividend.claimed {
|
||
return Err("Dividend already claimed".to_string());
|
||
}
|
||
|
||
// 检查分配记录状态
|
||
let record = self.records.get(dividend_id)
|
||
.ok_or_else(|| "Dividend record not found".to_string())?;
|
||
|
||
if record.status != DividendStatus::Completed {
|
||
return Err("Dividend distribution not completed yet".to_string());
|
||
}
|
||
|
||
// 标记为已领取
|
||
dividend.claimed = true;
|
||
dividend.claim_time = Some(Self::current_timestamp());
|
||
|
||
Ok(dividend.net_amount)
|
||
}
|
||
|
||
/// 获取账户的所有股息记录
|
||
pub fn get_dividends(&self, account: &str) -> Vec<PersonalDividend> {
|
||
self.personal_dividends
|
||
.get(account)
|
||
.cloned()
|
||
.unwrap_or_default()
|
||
}
|
||
|
||
/// 获取账户的未领取股息
|
||
pub fn get_unclaimed_dividends(&self, account: &str) -> Vec<PersonalDividend> {
|
||
self.get_dividends(account)
|
||
.into_iter()
|
||
.filter(|d| !d.claimed)
|
||
.collect()
|
||
}
|
||
|
||
/// 获取账户的总未领取股息金额
|
||
pub fn get_total_unclaimed_amount(&self, account: &str) -> u64 {
|
||
self.get_unclaimed_dividends(account)
|
||
.iter()
|
||
.map(|d| d.net_amount)
|
||
.sum()
|
||
}
|
||
|
||
/// 获取分配记录
|
||
pub fn get_dividend_record(&self, dividend_id: &str) -> Option<&DividendRecord> {
|
||
self.records.get(dividend_id)
|
||
}
|
||
|
||
/// 取消股息分配
|
||
pub fn cancel_dividend(&mut self, dividend_id: &str) -> Result<(), String> {
|
||
let record = self.records.get_mut(dividend_id)
|
||
.ok_or_else(|| "Dividend not found".to_string())?;
|
||
|
||
if record.status != DividendStatus::Pending {
|
||
return Err("Can only cancel pending dividends".to_string());
|
||
}
|
||
|
||
record.status = DividendStatus::Cancelled;
|
||
Ok(())
|
||
}
|
||
|
||
/// 获取证券的所有股息记录
|
||
pub fn get_security_dividends(&self, security_id: &[u8; 32]) -> Vec<DividendRecord> {
|
||
self.records
|
||
.values()
|
||
.filter(|r| &r.security_id == security_id)
|
||
.cloned()
|
||
.collect()
|
||
}
|
||
|
||
/// 计算账户的累计股息收入
|
||
pub fn calculate_total_dividend_income(&self, account: &str) -> (u64, u64, u64) {
|
||
let dividends = self.get_dividends(account);
|
||
|
||
let total_gross: u64 = dividends.iter().map(|d| d.gross_amount).sum();
|
||
let total_tax: u64 = dividends.iter().map(|d| d.tax_amount).sum();
|
||
let total_net: u64 = dividends.iter().map(|d| d.net_amount).sum();
|
||
|
||
(total_gross, total_tax, total_net)
|
||
}
|
||
|
||
/// 获取当前时间戳
|
||
fn current_timestamp() -> u64 {
|
||
std::time::SystemTime::now()
|
||
.duration_since(std::time::UNIX_EPOCH)
|
||
.unwrap()
|
||
.as_secs()
|
||
}
|
||
}
|
||
|
||
impl Default for DividendEngine {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_declare_dividend() {
|
||
let mut engine = DividendEngine::new();
|
||
let security_id = [1u8; 32];
|
||
|
||
let mut holders = HashMap::new();
|
||
holders.insert("investor1".to_string(), 1000);
|
||
holders.insert("investor2".to_string(), 500);
|
||
|
||
let result = engine.declare_dividend(security_id, 10, 15, &holders);
|
||
assert!(result.is_ok());
|
||
|
||
let dividend_id = result.unwrap();
|
||
let record = engine.get_dividend_record(÷nd_id).unwrap();
|
||
|
||
assert_eq!(record.amount_per_share, 10);
|
||
assert_eq!(record.total_amount, 15000); // (1000 + 500) * 10
|
||
assert_eq!(record.beneficiary_count, 2);
|
||
assert_eq!(record.tax_rate, 15);
|
||
}
|
||
|
||
#[test]
|
||
fn test_distribute_and_claim_dividend() {
|
||
let mut engine = DividendEngine::new();
|
||
let security_id = [1u8; 32];
|
||
|
||
let mut holders = HashMap::new();
|
||
holders.insert("investor1".to_string(), 1000);
|
||
|
||
let dividend_id = engine.declare_dividend(security_id, 10, 15, &holders).unwrap();
|
||
|
||
// 分配股息
|
||
engine.distribute_dividend(÷nd_id).unwrap();
|
||
|
||
// 领取股息
|
||
let amount = engine.claim_dividend("investor1", ÷nd_id).unwrap();
|
||
|
||
// 税前: 1000 * 10 = 10000
|
||
// 税额: 10000 * 15% = 1500
|
||
// 税后: 10000 - 1500 = 8500
|
||
assert_eq!(amount, 8500);
|
||
|
||
// 再次领取应该失败
|
||
let result = engine.claim_dividend("investor1", ÷nd_id);
|
||
assert!(result.is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_unclaimed_dividends() {
|
||
let mut engine = DividendEngine::new();
|
||
let security_id = [1u8; 32];
|
||
|
||
let mut holders = HashMap::new();
|
||
holders.insert("investor1".to_string(), 1000);
|
||
|
||
let dividend_id = engine.declare_dividend(security_id, 10, 15, &holders).unwrap();
|
||
engine.distribute_dividend(÷nd_id).unwrap();
|
||
|
||
let unclaimed = engine.get_unclaimed_dividends("investor1");
|
||
assert_eq!(unclaimed.len(), 1);
|
||
assert_eq!(unclaimed[0].net_amount, 8500);
|
||
|
||
let total = engine.get_total_unclaimed_amount("investor1");
|
||
assert_eq!(total, 8500);
|
||
|
||
// 领取后应该没有未领取股息
|
||
engine.claim_dividend("investor1", ÷nd_id).unwrap();
|
||
let unclaimed = engine.get_unclaimed_dividends("investor1");
|
||
assert_eq!(unclaimed.len(), 0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_cancel_dividend() {
|
||
let mut engine = DividendEngine::new();
|
||
let security_id = [1u8; 32];
|
||
|
||
let mut holders = HashMap::new();
|
||
holders.insert("investor1".to_string(), 1000);
|
||
|
||
let dividend_id = engine.declare_dividend(security_id, 10, 15, &holders).unwrap();
|
||
|
||
// 取消分配
|
||
engine.cancel_dividend(÷nd_id).unwrap();
|
||
|
||
let record = engine.get_dividend_record(÷nd_id).unwrap();
|
||
assert_eq!(record.status, DividendStatus::Cancelled);
|
||
|
||
// 已取消的分配不能执行
|
||
let result = engine.distribute_dividend(÷nd_id);
|
||
assert!(result.is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_total_dividend_income() {
|
||
let mut engine = DividendEngine::new();
|
||
let security_id = [1u8; 32];
|
||
|
||
let mut holders = HashMap::new();
|
||
holders.insert("investor1".to_string(), 1000);
|
||
|
||
// 第一次分配
|
||
let div1 = engine.declare_dividend(security_id, 10, 15, &holders).unwrap();
|
||
engine.distribute_dividend(&div1).unwrap();
|
||
engine.claim_dividend("investor1", &div1).unwrap();
|
||
|
||
// 第二次分配
|
||
let div2 = engine.declare_dividend(security_id, 20, 15, &holders).unwrap();
|
||
engine.distribute_dividend(&div2).unwrap();
|
||
engine.claim_dividend("investor1", &div2).unwrap();
|
||
|
||
let (gross, tax, net) = engine.calculate_total_dividend_income("investor1");
|
||
|
||
// 第一次: 10000税前, 1500税, 8500税后
|
||
// 第二次: 20000税前, 3000税, 17000税后
|
||
// 总计: 30000税前, 4500税, 25500税后
|
||
assert_eq!(gross, 30000);
|
||
assert_eq!(tax, 4500);
|
||
assert_eq!(net, 25500);
|
||
}
|
||
}
|