添加总计
This commit is contained in:
parent
0ee56aad24
commit
4b052d8b2a
@ -10,13 +10,15 @@
|
||||
<cargoProject FILE="$PROJECT_DIR$/bookkeeper/Cargo.toml" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="76b3b902-7a5c-4bcd-9c1b-8241d748fb44" name="更改" comment="更新版本号和部署脚本">
|
||||
<list default="true" id="76b3b902-7a5c-4bcd-9c1b-8241d748fb44" name="更改" comment="清仓筛选">
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/Cargo.lock" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/Cargo.lock" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/Cargo.toml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/migration/src/m20220101_000001_create_table.rs" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/migration/src/m20220101_000001_create_table.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/migration/src/m20241230_160326_gain_evolution.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/src/main.rs" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/src/main.rs" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/templates/index.html.j2" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/templates/index.html.j2" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/templates/nav.html.j2" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/templates/nav.html.j2" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/templates/tx.html.j2" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/templates/tx.html.j2" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@ -83,7 +85,7 @@
|
||||
</component>
|
||||
<component name="RunManager" selected="Cargo.Run bookkeeper">
|
||||
<configuration name="Run bookkeeper" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
|
||||
<option name="buildProfileId" value="release" />
|
||||
<option name="buildProfileId" value="dev" />
|
||||
<option name="command" value="run --package bookkeeper --bin bookkeeper" />
|
||||
<option name="workingDirectory" value="file://$PROJECT_DIR$/bookkeeper" />
|
||||
<envs />
|
||||
@ -154,7 +156,9 @@
|
||||
<workItem from="1735470376833" duration="16014000" />
|
||||
<workItem from="1735553493290" duration="16207000" />
|
||||
<workItem from="1736238929025" duration="646000" />
|
||||
<workItem from="1736239592084" duration="1828000" />
|
||||
<workItem from="1736239592084" duration="2278000" />
|
||||
<workItem from="1737809478583" duration="76000" />
|
||||
<workItem from="1739197188908" duration="1079000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Add bookkeeper">
|
||||
<option name="closed" value="true" />
|
||||
@ -252,7 +256,15 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1736241063964</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="13" />
|
||||
<task id="LOCAL-00013" summary="清仓筛选">
|
||||
<option name="closed" value="true" />
|
||||
<created>1736241771673</created>
|
||||
<option name="number" value="00013" />
|
||||
<option name="presentableId" value="LOCAL-00013" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1736241771673</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="14" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@ -282,7 +294,8 @@
|
||||
<MESSAGE value="Refine gain algorithm and some UI update" />
|
||||
<MESSAGE value="Filter by name" />
|
||||
<MESSAGE value="更新版本号和部署脚本" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="更新版本号和部署脚本" />
|
||||
<MESSAGE value="清仓筛选" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="清仓筛选" />
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
|
||||
2
bookkeeper/Cargo.lock
generated
2
bookkeeper/Cargo.lock
generated
@ -423,7 +423,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bookkeeper"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"entity",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "bookkeeper"
|
||||
version = "0.1.2"
|
||||
version = "0.1.3"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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?;
|
||||
}
|
||||
|
||||
@ -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("/?<last>&<name>&<is_done>")]
|
||||
async fn index(conn: Connection<'_, Db>, last: Option<u32>, name: Option<&str>, is_done: Option<bool>) -> Template {
|
||||
async fn index(
|
||||
conn: Connection<'_, Db>,
|
||||
last: Option<u32>,
|
||||
name: Option<&str>,
|
||||
is_done: Option<bool>,
|
||||
) -> 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<transaction::Model> = 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<u32>, 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/<id>")]
|
||||
async fn get_tx(conn: Connection<'_, Db>, id: i32) -> Result<Template, Status> {
|
||||
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/<id>", data = "<data>")]
|
||||
async fn post_tx(data: Form<TransModel<'_>>, conn: Connection<'_, Db>, id: i32) -> Result<Redirect, Status> {
|
||||
async fn post_tx(
|
||||
data: Form<TransModel<'_>>,
|
||||
conn: Connection<'_, Db>,
|
||||
id: i32,
|
||||
) -> Result<Redirect, Status> {
|
||||
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/<id>")]
|
||||
async fn delete_tx(conn: Connection<'_, Db>, id: i32) -> Result<Redirect, Status> {
|
||||
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<Redirect, Status
|
||||
|
||||
#[get("/add")]
|
||||
async fn get_add() -> Template {
|
||||
Template::render("tx", context! {
|
||||
r: (),
|
||||
target: "/add",
|
||||
})
|
||||
Template::render(
|
||||
"tx",
|
||||
context! {
|
||||
r: (),
|
||||
target: "/add",
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[post("/add", data = "<trans>")]
|
||||
@ -191,7 +228,7 @@ fn translate(trans: Form<TransModel>) -> transaction::ActiveModel {
|
||||
if let Some(split_pieces) = &trans.split_pieces {
|
||||
let mut sp: Vec<SplitPiece> = 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<TransModel>) -> 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<TransModel>) -> 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"))
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,8 +15,8 @@
|
||||
<th>卖出价</th>
|
||||
<th>卖量</th>
|
||||
<th><abbr title="卖出金额-佣金-各种手续费">实际卖出</abbr></th>
|
||||
<th>收益</th>
|
||||
<th>实际收益</th>
|
||||
<th>收益: {{ gain|float|round(2) }} ({{ ((pct|float)*100)|round(2) }} %)</th>
|
||||
<th>实际收益: {{ net_gain|float|round(2) }} ({{ ((net_pct|float)*100)|round(2) }} %)</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
@ -36,7 +36,7 @@
|
||||
<th><label class="label">卖出日期</label></th>
|
||||
<th><label class="label">卖出价</label></th>
|
||||
<th><label class="label">成交量</label></th>
|
||||
<th><label class="label">实际卖出入</label></th>
|
||||
<th><label class="label">实际卖出</label></th>
|
||||
<th style="min-width: 4rem; vertical-align: middle;">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user