diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 1074463..9da464d 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -10,18 +10,21 @@ - - + + + + - + + - + - @@ -242,7 +253,8 @@ - diff --git a/bookkeeper/Cargo.lock b/bookkeeper/Cargo.lock index f04c3f5..9c22cd7 100644 --- a/bookkeeper/Cargo.lock +++ b/bookkeeper/Cargo.lock @@ -1836,6 +1836,8 @@ name = "migration" version = "0.1.0" dependencies = [ "async-std", + "rust_decimal", + "sea-orm", "sea-orm-migration", ] diff --git a/bookkeeper/migration/Cargo.toml b/bookkeeper/migration/Cargo.toml index 21dcb82..c5d65ef 100644 --- a/bookkeeper/migration/Cargo.toml +++ b/bookkeeper/migration/Cargo.toml @@ -10,6 +10,7 @@ path = "src/lib.rs" [dependencies] async-std = { version = "1", features = ["attributes", "tokio1"] } +rust_decimal = "1.36.0" [dependencies.sea-orm-migration] version = "1.1.0" @@ -22,3 +23,15 @@ features = [ "sqlx-sqlite", "runtime-tokio-rustls", ] + +[dependencies.sea-orm] +version = "1.1.0" +features = [ + "sqlx-sqlite", + "runtime-tokio-rustls", + "macros", + "with-rust_decimal", + "with-chrono", + "with-uuid", + "with-json" +] diff --git a/bookkeeper/migration/src/lib.rs b/bookkeeper/migration/src/lib.rs index 2c605af..3690f7e 100644 --- a/bookkeeper/migration/src/lib.rs +++ b/bookkeeper/migration/src/lib.rs @@ -1,12 +1,16 @@ pub use sea_orm_migration::prelude::*; mod m20220101_000001_create_table; +mod m20241230_160326_gain_evolution; pub struct Migrator; #[async_trait::async_trait] impl MigratorTrait for Migrator { fn migrations() -> Vec> { - vec![Box::new(m20220101_000001_create_table::Migration)] + vec![ + Box::new(m20220101_000001_create_table::Migration), + Box::new(m20241230_160326_gain_evolution::Migration), + ] } } diff --git a/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs b/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs new file mode 100644 index 0000000..79638da --- /dev/null +++ b/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs @@ -0,0 +1,63 @@ +use std::str::FromStr; +use sea_orm_migration::prelude::*; + +use rust_decimal::Decimal; +use sea_orm::entity::prelude::*; +use sea_orm::ActiveValue::Set; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq,)] +#[sea_orm(table_name = "transaction")] +pub struct Model { + #[sea_orm(primary_key)] + pub id: i32, + pub code: i32, + pub name: String, + pub date: Date, + pub buy: Decimal, + pub volume: i32, + pub net_buy: Decimal, + #[sea_orm(column_type = "JsonBinary", nullable)] + pub split_pieces: Option, + pub gain: Option, + pub net_gain: Option, + pub is_done: bool, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} +impl ActiveModelBehavior for ActiveModel {} +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + let db = manager.get_connection(); + let records: Vec = Entity::find() + .filter(Column::Gain.is_not_null()) + .all(db) + .await?; + for record in records { + let mut r: ActiveModel = record.into(); + let pcs = r.split_pieces.clone().unwrap().unwrap(); + let pieces = pcs.as_array().unwrap(); + let mut total_sell = Decimal::from_str("0.0").unwrap(); + let mut total_net_sell = Decimal::from_str("0.0").unwrap(); + for piece in pieces { + let p = piece.as_object().unwrap(); + total_sell += Decimal::from_str(p.get("sell").unwrap().as_str().unwrap()).unwrap() * + Decimal::from(p.get("volume").unwrap().as_i64().unwrap()); + total_net_sell += Decimal::from_str(p.get("net_sell").unwrap().as_str().unwrap()).unwrap(); + } + r.gain = Set(Some(total_sell - r.buy.clone().unwrap() * Decimal::from(r.volume.clone().unwrap()))); + r.net_gain = Set(Some(total_net_sell - r.net_buy.clone().unwrap())); + r.update(db).await?; + } + Ok(()) + } + + async fn down(&self, _manager: &SchemaManager) -> Result<(), DbErr> { + // Replace the sample below with your own migration scripts + Ok(()) + } +} diff --git a/bookkeeper/src/main.rs b/bookkeeper/src/main.rs index a687543..c5182f5 100644 --- a/bookkeeper/src/main.rs +++ b/bookkeeper/src/main.rs @@ -11,7 +11,7 @@ use rocket::fairing::{self, AdHoc}; use rocket::form::Form; use rocket::response::{Flash, Redirect}; use rocket::{Build, Rocket}; -use rocket_dyn_templates::{context, Template}; +use rocket_dyn_templates::{context, Engines, Template}; use rust_decimal::Decimal; use sea_orm::query::*; use sea_orm::ActiveValue::Set; @@ -57,10 +57,15 @@ impl sea_orm_rocket::Pool for SeaOrmPool { } } -#[get("/")] -async fn index(conn: Connection<'_, Db>) -> Template { - let rows: Vec = TE::find() - .filter(transaction::Column::Date.gte(Local::now() - TimeDelta::days(30))) +#[get("/?")] +async fn index(conn: Connection<'_, Db>, last: Option) -> Template { + let mut partial = TE::find(); + + if let Some(last) = last { + partial = partial.filter(transaction::Column::Date.gte(Local::now() - TimeDelta::days(last as i64))); + } + + let rows: Vec = partial .order_by_desc(transaction::Column::Id) .all(conn.into_inner()) .await @@ -189,15 +194,15 @@ fn translate(trans: Form) -> transaction::ActiveModel { acc + e.sell * Decimal::from(e.volume) }); let buy = Decimal::from_str(trans.buy).unwrap() * Decimal::from(total); - record.gain = Set(Option::from((sell_int - buy) / buy * Decimal::from(100))); + record.gain = Set(Some(sell_int - buy)); let net_sell_int = sp.iter().fold(Decimal::from(0), |acc, e| { acc + e.net_sell }); let net_buy = Decimal::from_str(trans.net_buy).unwrap(); - record.net_gain = Set(Option::from((net_sell_int - net_buy) / net_buy * Decimal::from(100))); + record.net_gain = Set(Some(net_sell_int - net_buy)); } let v: serde_json::value::Value = serde_json::to_value(sp).unwrap(); - record.split_pieces = Set(Option::from(v)); + record.split_pieces = Set(Some(v)); } record @@ -214,7 +219,15 @@ async fn rocket() -> _ { rocket::build() .attach(Db::init()) .attach(AdHoc::try_on_ignite("Migrations", run_migrations)) - .attach(Template::fairing()) + .attach(Template::custom(|engines: &mut Engines| { + engines.minijinja.add_filter("padLeft", |value: String, length: usize| -> String { + if value.len() < length { + "0".repeat(length - value.len()) + value.as_str() + } else { + value + } + }) + })) .mount("/", routes![index, add, get_add, get_tx, post_tx, delete_tx]) .mount("/static", FileServer::from("static")) } \ No newline at end of file diff --git a/bookkeeper/templates/index.html.j2 b/bookkeeper/templates/index.html.j2 index 427be94..fb8bebe 100644 --- a/bookkeeper/templates/index.html.j2 +++ b/bookkeeper/templates/index.html.j2 @@ -4,7 +4,7 @@
- + @@ -26,18 +26,30 @@ {% for p in r.split_pieces %} {% if loop.index == 1 %} - + - + - + - + - - - + + + @@ -51,12 +63,12 @@ {% endfor %} {% else %} - + - + - + @@ -68,8 +80,9 @@ {% endif %} {% endfor %} - - + +
代码 名称 买入日期
{{ r.code }}{{ r.code|string|padLeft(6) }} {{ r.name }} {{ r.date }}{{ r.buy }}{{ r.buy|float|round(4) }} {{ r.volume }}{{ r.net_buy }}{{ r.net_buy|float|round(2) }} {{ p.date or "" }}{{ p.sell or "" }}{{ (p.sell or 0)|float|round(4) }} {{ p.volume or "" }}{{ p.net_sell or "" }}{{ (r.gain or 0)|float|round(4) }}%{{ (r.net_gain or 0)|float|round(4) }}%{{ (p.net_sell or 0)|float|round(2) }} + {% if r.gain %} + {{ r.gain|float|round(2) }} ({{ ((r.gain|float)/(r.buy|float)/(r.volume|float)*100)|round(2) }}%) + {% else %} + - + {% endif %} + + {% if r.net_gain %} + {{ r.net_gain|float|round(2) }} ({{ ((r.net_gain|float)/(r.net_buy|float)*100)|round(2) }}%) + {% else %} + - + {% endif %} +  
{{ r.code }}{{ r.code|string|padLeft(6) }} {{ r.name }} {{ r.date }}{{ r.buy }}{{ r.buy|float|round(4) }} {{ r.volume }}{{ r.net_buy }}{{ r.net_buy|float|round(2) }}
添加
添加     高级筛选
diff --git a/bookkeeper/templates/tx.html.j2 b/bookkeeper/templates/tx.html.j2 index 32fc726..c625039 100644 --- a/bookkeeper/templates/tx.html.j2 +++ b/bookkeeper/templates/tx.html.j2 @@ -24,9 +24,9 @@ - + - + @@ -45,18 +45,18 @@ {% for split in r.split_pieces %} - + - + 删除 {% endfor %} {% else %} - + - + 删除 {% endif %}