Files
kakeibo/backend/src/db/migrations.rs
T
suphicakir 267d292a90 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>
2026-11-13 00:35:23 +03:00

121 lines
3.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Basit şema migration mekanizması (DATA_MODEL.md §3.15).
//!
//! Her migration bir kez çalışır; `schema_migrations` koleksiyonuna
//! `{ _id: sürüm, name, applied_at }` olarak kaydedilir.
//! Yeni migration eklemek: `MIGRATIONS` listesine bir sonraki sürüm numarasıyla ekle.
use std::{future::Future, pin::Pin};
use mongodb::{
Database,
bson::{DateTime, Document, doc},
error::Result,
};
use super::col;
type MigrationFn =
for<'a> fn(&'a Database) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>;
struct Migration {
version: i32,
name: &'static str,
run: MigrationFn,
}
/// Sıralı migration listesi. Sürümler 1'den başlar ve atlamadan artar.
const MIGRATIONS: &[Migration] = &[
Migration {
version: 1,
name: "initial",
run: |db| Box::pin(m001_initial(db)),
},
Migration {
version: 2,
name: "default_assets",
run: |db| Box::pin(m002_default_assets(db)),
},
];
/// Uygulanmamış migration'ları sırayla çalıştırır.
pub async fn run(db: &Database) -> Result<()> {
let coll = db.collection::<Document>(col::SCHEMA_MIGRATIONS);
for m in MIGRATIONS {
if coll.find_one(doc! { "_id": m.version }).await?.is_some() {
continue;
}
tracing::info!("Migration çalışıyor: v{} ({})", m.version, m.name);
(m.run)(db).await?;
coll.insert_one(doc! {
"_id": m.version,
"name": m.name,
"applied_at": DateTime::now(),
})
.await?;
}
Ok(())
}
/// v1: Tüm koleksiyonları açıkça oluşturur.
/// (Transaction içinde ilk kez yazılan koleksiyonlar sorun çıkarmasın diye önceden var olsunlar.)
async fn m001_initial(db: &Database) -> Result<()> {
let existing = db.list_collection_names().await?;
let all = col::HOUSEHOLD_SCOPED
.iter()
.chain(&[col::EXCHANGE_RATES, col::SCHEMA_MIGRATIONS]);
for name in all {
if !existing.iter().any(|e| e == name) {
db.create_collection(*name).await?;
}
}
Ok(())
}
/// v2: Faz 8'den önce açılmış hanelere varsayılan varlıklar (TL, altınlar, USD, EUR).
/// Varlığı olan haneye dokunmaz → tekrar çalışsa da çift kayıt olmaz.
async fn m002_default_assets(db: &Database) -> Result<()> {
#[derive(serde::Deserialize)]
struct Household {
#[serde(rename = "_id")]
id: mongodb::bson::Uuid,
}
let assets = db.collection::<Document>(col::ASSETS);
let mut cursor = db
.collection::<Household>(col::HOUSEHOLDS)
.find(doc! { "deleted_at": null })
.projection(doc! { "_id": 1 })
.await?;
while cursor.advance().await? {
let hid = cursor.deserialize_current()?.id;
if assets
.find_one(doc! { "household_id": hid })
.await?
.is_none()
{
assets
.insert_many(crate::services::seed::assets(hid))
.await?;
tracing::info!("Varsayılan varlıklar eklendi: hane {hid}");
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn versions_start_at_1_and_are_sequential() {
for (i, m) in MIGRATIONS.iter().enumerate() {
assert_eq!(
m.version,
i as i32 + 1,
"migration sürümleri ardışık olmalı"
);
}
}
}