267d292a90
- 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>
121 lines
3.6 KiB
Rust
121 lines
3.6 KiB
Rust
//! İndeksler (DATA_MODEL.md §4). Açılışta her seferinde çağrılır;
|
||
//! MongoDB zaten var olan aynı indeksi yeniden oluşturmaz.
|
||
|
||
use mongodb::{
|
||
Database, IndexModel,
|
||
bson::{Document, doc},
|
||
error::Result,
|
||
options::IndexOptions,
|
||
};
|
||
|
||
use super::col;
|
||
|
||
pub async fn ensure(db: &Database) -> Result<()> {
|
||
for (collection, indexes) in definitions() {
|
||
db.collection::<Document>(collection)
|
||
.create_indexes(indexes)
|
||
.await?;
|
||
}
|
||
tracing::info!("İndeksler hazır");
|
||
Ok(())
|
||
}
|
||
|
||
fn idx(keys: Document) -> IndexModel {
|
||
IndexModel::builder().keys(keys).build()
|
||
}
|
||
|
||
fn idx_opts(keys: Document, options: IndexOptions) -> IndexModel {
|
||
IndexModel::builder().keys(keys).options(options).build()
|
||
}
|
||
|
||
fn unique() -> IndexOptions {
|
||
IndexOptions::builder().unique(true).build()
|
||
}
|
||
|
||
fn definitions() -> Vec<(&'static str, Vec<IndexModel>)> {
|
||
let hp = || idx(doc! { "household_id": 1, "period_id": 1 });
|
||
|
||
let mut defs = vec![
|
||
(col::USERS, vec![idx_opts(doc! { "email": 1 }, unique())]),
|
||
(col::INVITES, vec![idx_opts(doc! { "code": 1 }, unique())]),
|
||
(
|
||
col::PERIODS,
|
||
vec![
|
||
idx(doc! { "household_id": 1, "start_date": 1 }),
|
||
// Hanede en fazla bir açık dönem
|
||
idx_opts(
|
||
doc! { "household_id": 1 },
|
||
IndexOptions::builder()
|
||
.name("one_open_period_per_household".to_string())
|
||
.unique(true)
|
||
.partial_filter_expression(doc! { "status": "open" })
|
||
.build(),
|
||
),
|
||
],
|
||
),
|
||
(
|
||
col::CATEGORIES,
|
||
vec![idx(doc! { "household_id": 1, "sort_order": 1 })],
|
||
),
|
||
(
|
||
col::PAYMENT_METHODS,
|
||
vec![idx(doc! { "household_id": 1, "sort_order": 1 })],
|
||
),
|
||
(col::PLAN_ITEMS, vec![hp()]),
|
||
(col::BUDGETS, vec![hp()]),
|
||
(col::NOTES, vec![hp()]),
|
||
(
|
||
col::ENTRIES,
|
||
vec![
|
||
idx(doc! { "household_id": 1, "period_id": 1, "date": 1 }),
|
||
idx(doc! { "household_id": 1, "date": 1 }),
|
||
],
|
||
),
|
||
(
|
||
col::CARRYOVERS,
|
||
vec![idx(doc! { "household_id": 1, "to_period_id": 1 })],
|
||
),
|
||
(
|
||
col::ASSETS,
|
||
vec![idx(doc! { "household_id": 1, "sort_order": 1 })],
|
||
),
|
||
(
|
||
col::ASSET_TRANSACTIONS,
|
||
vec![idx(doc! { "household_id": 1, "asset_id": 1, "date": 1 })],
|
||
),
|
||
(
|
||
col::EXCHANGE_RATES,
|
||
vec![idx(doc! { "code": 1, "household_id": 1, "rate_date": -1 })],
|
||
),
|
||
];
|
||
|
||
// Senkronizasyon (Faz 11): "şu tarihten sonra değişenler" sorgusu.
|
||
for &name in col::HOUSEHOLD_SCOPED {
|
||
let sync = idx(doc! { "household_id": 1, "updated_at": 1 });
|
||
match defs.iter_mut().find(|(c, _)| *c == name) {
|
||
Some((_, list)) => list.push(sync),
|
||
None => defs.push((name, vec![sync])),
|
||
}
|
||
}
|
||
|
||
defs
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn every_household_collection_has_sync_index() {
|
||
let defs = definitions();
|
||
for &name in col::HOUSEHOLD_SCOPED {
|
||
let (_, list) = defs.iter().find(|(c, _)| *c == name).unwrap();
|
||
assert!(
|
||
list.iter()
|
||
.any(|i| i.keys == doc! { "household_id": 1, "updated_at": 1 }),
|
||
"{name} koleksiyonunda senkronizasyon indeksi yok"
|
||
);
|
||
}
|
||
}
|
||
}
|