From 4b052d8b2a808713568d5ee5d794709afaca004d Mon Sep 17 00:00:00 2001 From: wjsjwr Date: Mon, 10 Feb 2025 22:40:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E6=80=BB=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/workspace.xml | 25 +++- bookkeeper/Cargo.lock | 2 +- bookkeeper/Cargo.toml | 2 +- .../src/m20220101_000001_create_table.rs | 59 +++++---- .../src/m20241230_160326_gain_evolution.rs | 15 ++- bookkeeper/src/main.rs | 124 ++++++++++++------ bookkeeper/templates/index.html.j2 | 4 +- bookkeeper/templates/tx.html.j2 | 2 +- 8 files changed, 146 insertions(+), 87 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index ec37514..321fb86 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -10,13 +10,15 @@ - + + + - + - @@ -282,7 +294,8 @@ - diff --git a/bookkeeper/Cargo.lock b/bookkeeper/Cargo.lock index 645c0ed..858dfa0 100644 --- a/bookkeeper/Cargo.lock +++ b/bookkeeper/Cargo.lock @@ -423,7 +423,7 @@ dependencies = [ [[package]] name = "bookkeeper" -version = "0.1.2" +version = "0.1.3" dependencies = [ "chrono", "entity", diff --git a/bookkeeper/Cargo.toml b/bookkeeper/Cargo.toml index 560a99d..a79b7d2 100644 --- a/bookkeeper/Cargo.toml +++ b/bookkeeper/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "bookkeeper" -version = "0.1.2" +version = "0.1.3" edition = "2021" [dependencies] diff --git a/bookkeeper/migration/src/m20220101_000001_create_table.rs b/bookkeeper/migration/src/m20220101_000001_create_table.rs index 2ac5935..bd2b2f2 100644 --- a/bookkeeper/migration/src/m20220101_000001_create_table.rs +++ b/bookkeeper/migration/src/m20220101_000001_create_table.rs @@ -6,36 +6,37 @@ pub struct Migration; #[async_trait::async_trait] impl MigrationTrait for Migration { async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { - - manager.create_table( - Table::create() - .table(User::Table) - .if_not_exists() - .col(pk_auto(User::Id)) - .col(string(User::Name)) - .col(string(User::Password)) - .col(string(User::SessionToken)) - .to_owned(), - ) + manager + .create_table( + Table::create() + .table(User::Table) + .if_not_exists() + .col(pk_auto(User::Id)) + .col(string(User::Name)) + .col(string(User::Password)) + .col(string(User::SessionToken)) + .to_owned(), + ) .await?; - manager.create_table( - Table::create() - .table(Transaction::Table) - .if_not_exists() - .col(pk_auto(Transaction::Id)) - .col(integer(Transaction::Code)) - .col(string(Transaction::Name)) - .col(date(Transaction::Date)) - .col(decimal(Transaction::Buy)) - .col(integer(Transaction::Volume)) - .col(decimal(Transaction::NetBuy)) - .col(json_binary_null(Transaction::SplitPieces)) - .col(decimal_null(Transaction::Gain)) - .col(decimal_null(Transaction::NetGain)) - .col(boolean(Transaction::IsDone)) - .to_owned(), - ) + manager + .create_table( + Table::create() + .table(Transaction::Table) + .if_not_exists() + .col(pk_auto(Transaction::Id)) + .col(integer(Transaction::Code)) + .col(string(Transaction::Name)) + .col(date(Transaction::Date)) + .col(decimal(Transaction::Buy)) + .col(integer(Transaction::Volume)) + .col(decimal(Transaction::NetBuy)) + .col(json_binary_null(Transaction::SplitPieces)) + .col(decimal_null(Transaction::Gain)) + .col(decimal_null(Transaction::NetGain)) + .col(boolean(Transaction::IsDone)) + .to_owned(), + ) .await?; Ok(()) @@ -80,4 +81,4 @@ enum Transaction { Gain, NetGain, IsDone, -} \ No newline at end of file +} diff --git a/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs b/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs index 79638da..ee3c961 100644 --- a/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs +++ b/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs @@ -1,11 +1,11 @@ -use std::str::FromStr; use sea_orm_migration::prelude::*; +use std::str::FromStr; use rust_decimal::Decimal; use sea_orm::entity::prelude::*; use sea_orm::ActiveValue::Set; -#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq,)] +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)] #[sea_orm(table_name = "transaction")] pub struct Model { #[sea_orm(primary_key)] @@ -45,11 +45,14 @@ impl MigrationTrait for Migration { 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(); + 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.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?; } diff --git a/bookkeeper/src/main.rs b/bookkeeper/src/main.rs index dc6c723..2df34e5 100644 --- a/bookkeeper/src/main.rs +++ b/bookkeeper/src/main.rs @@ -9,6 +9,8 @@ use entity::transaction::Entity as TE; use migration::MigratorTrait; use rocket::fairing::{self, AdHoc}; use rocket::form::Form; +use rocket::fs::FileServer; +use rocket::http::Status; use rocket::response::{Flash, Redirect}; use rocket::{Build, Rocket}; use rocket_dyn_templates::{context, Engines, Template}; @@ -17,11 +19,9 @@ use sea_orm::query::*; use sea_orm::ActiveValue::Set; use sea_orm::{ActiveModelTrait, ColumnTrait, ConnectOptions, DatabaseConnection, EntityTrait}; use sea_orm_rocket::{rocket::figment::Figment, Config, Connection, Database}; +use serde::{Deserialize, Serialize}; use std::str::FromStr; use std::time::Duration; -use rocket::http::Status; -use serde::{Deserialize, Serialize}; -use rocket::fs::FileServer; #[derive(sea_orm_rocket::Database, Debug)] #[database("bookkeeper")] @@ -58,21 +58,27 @@ impl sea_orm_rocket::Pool for SeaOrmPool { } #[get("/?&&")] -async fn index(conn: Connection<'_, Db>, last: Option, name: Option<&str>, is_done: Option) -> Template { +async fn index( + conn: Connection<'_, Db>, + last: Option, + name: Option<&str>, + is_done: 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))); + partial = partial + .filter(transaction::Column::Date.gte(Local::now() - TimeDelta::days(last as i64))); } if let Some(name) = name { partial = partial.filter(transaction::Column::Name.eq(name)); } - + if let Some(done) = is_done { partial = partial.filter(transaction::Column::IsDone.eq(done)); } - + let rows: Vec = partial .order_by_desc(transaction::Column::Date) .order_by_desc(transaction::Column::Id) @@ -81,11 +87,37 @@ async fn index(conn: Connection<'_, Db>, last: Option, name: Option<&str>, .unwrap(); let version = env!("CARGO_PKG_VERSION"); + + let mut bought = Decimal::from(0); + let mut net_bought = Decimal::from(0); + let mut gain = Decimal::from(0); + let mut net_gain = Decimal::from(0); + + for r in &rows { + if let Some(g) = r.gain { + bought += r.buy * Decimal::from(r.volume); + gain += g; + } + if let Some(ng) = r.net_gain { + net_bought += r.net_buy; + net_gain += ng; + } + } - Template::render("index", context! { - rows: rows, - version: version, - }) + let pct = gain / bought; + let net_pct = net_gain / net_bought; + + Template::render( + "index", + context! { + rows: rows, + version: version, + gain: gain, + net_gain: net_gain, + pct: pct, + net_pct: net_pct, + }, + ) } #[derive(FromForm, Debug)] @@ -110,7 +142,7 @@ struct SplitPieceForm<'r> { } #[derive(Deserialize, Serialize)] -struct SplitPiece{ +struct SplitPiece { date: NaiveDate, sell: Decimal, net_sell: Decimal, @@ -119,35 +151,37 @@ struct SplitPiece{ #[get("/tx/")] async fn get_tx(conn: Connection<'_, Db>, id: i32) -> Result { - let row = TE::find_by_id(id) - .one(conn.into_inner()) - .await - .unwrap(); + let row = TE::find_by_id(id).one(conn.into_inner()).await.unwrap(); match row { - None => { Err(Status::NotFound) }, - Some(row) => { - Ok(Template::render("tx", context! { + None => Err(Status::NotFound), + Some(row) => Ok(Template::render( + "tx", + context! { r: row, target: "/tx/".to_owned() + id.to_string().as_str(), - })) - } + }, + )), } } #[post("/tx/", data = "")] -async fn post_tx(data: Form>, conn: Connection<'_, Db>, id: i32) -> Result { +async fn post_tx( + data: Form>, + conn: Connection<'_, Db>, + id: i32, +) -> Result { let mut record = translate(data); record.id = Set(id); match record.update(conn.into_inner()).await { - Err(_) => { Err(Status::NotFound) }, - Ok(_) => { Ok(Redirect::to("/")) }, + Err(_) => Err(Status::NotFound), + Ok(_) => Ok(Redirect::to("/")), } } #[delete("/tx/")] async fn delete_tx(conn: Connection<'_, Db>, id: i32) -> Result { match TE::delete_by_id(id).exec(conn.into_inner()).await { - Err(_) => { Err(Status::InternalServerError) }, + Err(_) => Err(Status::InternalServerError), Ok(res) => { if res.rows_affected == 1 { Ok(Redirect::to("/")) @@ -160,10 +194,13 @@ async fn delete_tx(conn: Connection<'_, Db>, id: i32) -> Result Template { - Template::render("tx", context! { - r: (), - target: "/add", - }) + Template::render( + "tx", + context! { + r: (), + target: "/add", + }, + ) } #[post("/add", data = "")] @@ -191,7 +228,7 @@ fn translate(trans: Form) -> transaction::ActiveModel { if let Some(split_pieces) = &trans.split_pieces { let mut sp: Vec = Vec::new(); for split_piece in split_pieces.iter() { - sp.push(SplitPiece{ + sp.push(SplitPiece { date: NaiveDate::parse_from_str(split_piece.date, "%Y-%m-%d").unwrap(), sell: Decimal::from_str(split_piece.sell).unwrap(), net_sell: Decimal::from_str(split_piece.net_sell).unwrap(), @@ -199,7 +236,7 @@ fn translate(trans: Form) -> transaction::ActiveModel { }) } - let total: i32 = sp.iter().fold(0, |acc, e| { acc + e.volume }); + let total: i32 = sp.iter().fold(0, |acc, e| acc + e.volume); if total == trans.volume { record.is_done = Set(true); let sell_int = sp.iter().fold(Decimal::new(0, 4), |acc, e| { @@ -208,7 +245,7 @@ fn translate(trans: Form) -> transaction::ActiveModel { let buy = Decimal::from_str(trans.buy).unwrap() * Decimal::from(total); 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_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(Some(net_sell_int - net_buy)); } @@ -232,14 +269,19 @@ async fn rocket() -> _ { .attach(Db::init()) .attach(AdHoc::try_on_ignite("Migrations", run_migrations)) .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 - } - }) + 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( + "/", + 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 ec63b97..82a8f3f 100644 --- a/bookkeeper/templates/index.html.j2 +++ b/bookkeeper/templates/index.html.j2 @@ -15,8 +15,8 @@ 卖出价 卖量 实际卖出 - 收益 - 实际收益 + 收益: {{ gain|float|round(2) }} ({{ ((pct|float)*100)|round(2) }} %) + 实际收益: {{ net_gain|float|round(2) }} ({{ ((net_pct|float)*100)|round(2) }} %) 操作 diff --git a/bookkeeper/templates/tx.html.j2 b/bookkeeper/templates/tx.html.j2 index c625039..93fb086 100644 --- a/bookkeeper/templates/tx.html.j2 +++ b/bookkeeper/templates/tx.html.j2 @@ -36,7 +36,7 @@ - + 操作