feat(birikim): varlıklar ve alım/satım kayıtları (ortalama maliyet)

- Varsayılan varlıklar (TL, altınlar, USD, EUR) + migration v2 ile eski hanelere ekleme
- Döviz ekleme, varlık adı/aktifliği
- Alım/satım ekle, düzenle, sil; eldekinden fazla satış (geriye tarihli dahil) engellenir
- Ağırlıklı ortalama maliyet ve gerçekleşen kâr/zarar (hesaplanır, saklanmaz)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-13 00:35:23 +03:00
parent 1ef28b80a3
commit 267d292a90
24 changed files with 1516 additions and 12 deletions
+74
View File
@@ -0,0 +1,74 @@
//! `assets` ve `asset_transactions` — birikimler / varlıklar (DATA_MODEL.md §3.12, §3.13).
//!
//! Miktar ve birim fiyat **ölçekli tam sayı** (`_e6` = × 1.000.000); tutar kuruş.
//! Örnek: 5 gr altın → `quantity_e6: 5_000_000`; 3.250,00 TL/gr → `unit_price_e6: 3_250_000_000`.
use mongodb::bson::Uuid;
use serde::{Deserialize, Serialize};
use ts_rs::TS;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export)]
pub enum AssetKind {
/// Türk lirası (nakit birikim)
Cash,
Gold,
/// Döviz
Fx,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, TS)]
#[serde(rename_all = "lowercase")]
#[ts(export, rename = "AssetTxType")]
pub enum TxType {
Buy,
Sell,
}
impl TxType {
pub fn as_str(self) -> &'static str {
match self {
TxType::Buy => "buy",
TxType::Sell => "sell",
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct Asset {
#[serde(rename = "_id")]
pub id: Uuid,
pub kind: AssetKind,
/// `TRY`, `XAU_GRAM`, `USD`… (hanede tekil)
pub code: String,
pub name: String,
/// `₺`, `gr`, `adet`, `$`…
pub unit_label: String,
pub sort_order: i32,
pub is_active: bool,
pub version: i32,
}
#[derive(Debug, Clone, Deserialize)]
pub struct AssetTx {
#[serde(rename = "_id")]
pub id: Uuid,
pub asset_id: Uuid,
#[serde(rename = "type")]
pub tx_type: TxType,
/// `"YYYY-MM-DD"`
pub date: String,
pub quantity_e6: i64,
/// TL × 1e6, birim başına
pub unit_price_e6: i64,
/// Kuruş — girilen gerçek tutar (komisyon farkı olabilir); maliyet bundan hesaplanır
pub total: i64,
#[serde(default)]
pub note: Option<String>,
/// Dönem kapanışından geldiyse
#[serde(default)]
pub source_period_id: Option<Uuid>,
pub created_by: Uuid,
pub version: i32,
}