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 @@
-
+
+
+
-
+
@@ -83,7 +85,7 @@
-
+
@@ -154,7 +156,9 @@
-
+
+
+
@@ -252,7 +256,15 @@
1736241063964
-
+
+
+ 1736241771673
+
+
+
+ 1736241771673
+
+
@@ -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 @@
|
|
|
- |
+ |
操作 |