Refine gain algorithm and some UI update
This commit is contained in:
parent
d8aa433055
commit
41d7680920
@ -10,18 +10,21 @@
|
||||
<cargoProject FILE="$PROJECT_DIR$/bookkeeper/Cargo.toml" />
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="76b3b902-7a5c-4bcd-9c1b-8241d748fb44" name="更改" comment="Modify Tx and Delete Tx">
|
||||
<change afterPath="$PROJECT_DIR$/bookkeeper/.cargo/config.toml" afterDir="false" />
|
||||
<list default="true" id="76b3b902-7a5c-4bcd-9c1b-8241d748fb44" name="更改" comment="Release first version">
|
||||
<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/migration/Cargo.toml" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/migration/Cargo.toml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/bookkeeper/migration/src/lib.rs" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/migration/src/lib.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/base.html.j2" beforeDir="false" afterPath="$PROJECT_DIR$/bookkeeper/templates/base.html.j2" 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/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" />
|
||||
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||
</component>
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="RsBuildProfile:release" />
|
||||
<component name="ExecutionTargetManager" SELECTED_TARGET="RsBuildProfile:dev" />
|
||||
<component name="FileTemplateManagerImpl">
|
||||
<option name="RECENT_TEMPLATES">
|
||||
<list>
|
||||
@ -81,7 +84,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 />
|
||||
@ -150,7 +153,7 @@
|
||||
<workItem from="1735392290940" duration="620000" />
|
||||
<workItem from="1735392928494" duration="12910000" />
|
||||
<workItem from="1735470376833" duration="16014000" />
|
||||
<workItem from="1735553493290" duration="6862000" />
|
||||
<workItem from="1735553493290" duration="15888000" />
|
||||
</task>
|
||||
<task id="LOCAL-00001" summary="Add bookkeeper">
|
||||
<option name="closed" value="true" />
|
||||
@ -216,7 +219,15 @@
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1735562546317</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="9" />
|
||||
<task id="LOCAL-00009" summary="Release first version">
|
||||
<option name="closed" value="true" />
|
||||
<created>1735565490274</created>
|
||||
<option name="number" value="00009" />
|
||||
<option name="presentableId" value="LOCAL-00009" />
|
||||
<option name="project" value="LOCAL" />
|
||||
<updated>1735565490274</updated>
|
||||
</task>
|
||||
<option name="localTasksCounter" value="10" />
|
||||
<servers />
|
||||
</component>
|
||||
<component name="TypeScriptGeneratedFilesManager">
|
||||
@ -242,7 +253,8 @@
|
||||
<MESSAGE value="Implement add transaction" />
|
||||
<MESSAGE value="Update some settings" />
|
||||
<MESSAGE value="Modify Tx and Delete Tx" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="Modify Tx and Delete Tx" />
|
||||
<MESSAGE value="Release first version" />
|
||||
<option name="LAST_COMMIT_MESSAGE" value="Release first version" />
|
||||
</component>
|
||||
<component name="XSLT-Support.FileAssociations.UIState">
|
||||
<expand />
|
||||
|
||||
2
bookkeeper/Cargo.lock
generated
2
bookkeeper/Cargo.lock
generated
@ -1836,6 +1836,8 @@ name = "migration"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"rust_decimal",
|
||||
"sea-orm",
|
||||
"sea-orm-migration",
|
||||
]
|
||||
|
||||
|
||||
@ -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"
|
||||
]
|
||||
|
||||
@ -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<Box<dyn MigrationTrait>> {
|
||||
vec![Box::new(m20220101_000001_create_table::Migration)]
|
||||
vec![
|
||||
Box::new(m20220101_000001_create_table::Migration),
|
||||
Box::new(m20241230_160326_gain_evolution::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
63
bookkeeper/migration/src/m20241230_160326_gain_evolution.rs
Normal file
63
bookkeeper/migration/src/m20241230_160326_gain_evolution.rs
Normal file
@ -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<Json>,
|
||||
pub gain: Option<Decimal>,
|
||||
pub net_gain: Option<Decimal>,
|
||||
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<Model> = 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(())
|
||||
}
|
||||
}
|
||||
@ -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<transaction::Model> = TE::find()
|
||||
.filter(transaction::Column::Date.gte(Local::now() - TimeDelta::days(30)))
|
||||
#[get("/?<last>")]
|
||||
async fn index(conn: Connection<'_, Db>, last: Option<u32>) -> 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<transaction::Model> = partial
|
||||
.order_by_desc(transaction::Column::Id)
|
||||
.all(conn.into_inner())
|
||||
.await
|
||||
@ -189,15 +194,15 @@ fn translate(trans: Form<TransModel>) -> 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"))
|
||||
}
|
||||
@ -4,7 +4,7 @@
|
||||
<div class="">
|
||||
<table class="table is-fullwidth is-bordered is-hoverable">
|
||||
<thead>
|
||||
<tr>
|
||||
<tr style="position: sticky; top: 0; background-color: white;">
|
||||
<th style="width: fit-content">代码</th>
|
||||
<th style="width: fit-content">名称</th>
|
||||
<th>买入日期</th>
|
||||
@ -26,18 +26,30 @@
|
||||
{% for p in r.split_pieces %}
|
||||
<tr>
|
||||
{% if loop.index == 1 %}
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;">{{ r.code }}</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;">{{ r.code|string|padLeft(6) }}</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;">{{ r.name }}</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;" class="{% if not r.is_done %}is-warning{% endif %}">{{ r.date }}</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;">{{ r.buy }}</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;">{{ r.buy|float|round(4) }}</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;">{{ r.volume }}</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;">{{ r.net_buy }}</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;">{{ r.net_buy|float|round(2) }}</td>
|
||||
<td>{{ p.date or "" }}</td>
|
||||
<td>{{ p.sell or "" }}</td>
|
||||
<td>{{ (p.sell or 0)|float|round(4) }}</td>
|
||||
<td>{{ p.volume or "" }}</td>
|
||||
<td>{{ p.net_sell or "" }}</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;" class="{% if r.gain|float > 0 %}is-success{% else %}is-danger{% endif %}">{{ (r.gain or 0)|float|round(4) }}%</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;" class="{% if r.net_gain|float > 0 %}is-success{% else %}is-danger{% endif %}">{{ (r.net_gain or 0)|float|round(4) }}%</td>
|
||||
<td>{{ (p.net_sell or 0)|float|round(2) }}</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;" class="{% if r.gain|float > 0 %}is-success{% else %}is-danger{% endif %}">
|
||||
{% if r.gain %}
|
||||
{{ r.gain|float|round(2) }} ({{ ((r.gain|float)/(r.buy|float)/(r.volume|float)*100)|round(2) }}%)
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;" class="{% if r.net_gain|float > 0 %}is-success{% else %}is-danger{% endif %}">
|
||||
{% if r.net_gain %}
|
||||
{{ r.net_gain|float|round(2) }} ({{ ((r.net_gain|float)/(r.net_buy|float)*100)|round(2) }}%)
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td rowspan="{{ loop.length }}" style="vertical-align: middle;">
|
||||
<a row-id="{{ r.id }}" class="modify-row">改</a> <a row-id="{{ r.id }}" class="delete-row has-text-danger">删</a>
|
||||
</td>
|
||||
@ -51,12 +63,12 @@
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td>{{ r.code }}</td>
|
||||
<td>{{ r.code|string|padLeft(6) }}</td>
|
||||
<td>{{ r.name }}</td>
|
||||
<td class="is-warning">{{ r.date }}</td>
|
||||
<td>{{ r.buy }}</td>
|
||||
<td>{{ r.buy|float|round(4) }}</td>
|
||||
<td>{{ r.volume }}</td>
|
||||
<td>{{ r.net_buy }}</td>
|
||||
<td>{{ r.net_buy|float|round(2) }}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
@ -68,8 +80,9 @@
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<tr class="is-light">
|
||||
<td colspan="14" style="text-align: center"><a id="addEntry">添加</a></td>
|
||||
<tr class="is-light" style="position: sticky; bottom: 0;">
|
||||
<td colspan="14" style="text-align: center"><a id="addEntry">添加</a><span> </span><a
|
||||
id="filterEntry">高级筛选</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@ -24,9 +24,9 @@
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><input id="date" name="date" class="input" type="date" placeholder="YYYY-MM-DD" value="{{ r.date or '' }}"></td>
|
||||
<td><input id="buy" name="buy" class="input" type="number" placeholder="Buy" value="{{ r.buy or 0 }}"></td>
|
||||
<td><input id="buy" name="buy" class="input" type="text" placeholder="Buy" value="{{ r.buy or 0 }}"></td>
|
||||
<td><input id="volume" name="volume" class="input" type="number" placeholder="Volume" value="{{ r.volume or 0 }}"></td>
|
||||
<td><input id="net_buy" name="net_buy" class="input" type="number" placeholder="Net Buy" value="{{ r.net_buy or 0 }}"></td>
|
||||
<td><input id="net_buy" name="net_buy" class="input" type="text" placeholder="Net Buy" value="{{ r.net_buy or 0 }}"></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@ -45,18 +45,18 @@
|
||||
{% for split in r.split_pieces %}
|
||||
<tr>
|
||||
<td><input name="split_pieces[{{ loop.index0 }}].date" class="input" type="date" placeholder="YYYY-MM-DD" value="{{ split.date or '' }}"></td>
|
||||
<td><input name="split_pieces[{{ loop.index0 }}].sell" class="input" type="number" placeholder="Sell" value="{{ split.sell or '' }}"></td>
|
||||
<td><input name="split_pieces[{{ loop.index0 }}].sell" class="input" type="text" placeholder="Sell" value="{{ split.sell or '' }}"></td>
|
||||
<td><input name="split_pieces[{{ loop.index0 }}].volume" class="input" type="number" placeholder="Volume" value="{{ split.volume or '' }}"></td>
|
||||
<td><input name="split_pieces[{{ loop.index0 }}].net_sell" class="input" type="number" placeholder="Net Sell" value="{{ split.net_sell or '' }}"></td>
|
||||
<td><input name="split_pieces[{{ loop.index0 }}].net_sell" class="input" type="text" placeholder="Net Sell" value="{{ split.net_sell or '' }}"></td>
|
||||
<td><a onclick="deleteRow(this)">删除</a></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<tr>
|
||||
<td><input name="split_pieces[0].date" class="input" type="date" placeholder="YYYY-MM-DD"></td>
|
||||
<td><input name="split_pieces[0].sell" class="input" type="number" placeholder="Sell"></td>
|
||||
<td><input name="split_pieces[0].sell" class="input" type="text" placeholder="Sell"></td>
|
||||
<td><input name="split_pieces[0].volume" class="input" type="number" placeholder="Volume"></td>
|
||||
<td><input name="split_pieces[0].net_sell" class="input" type="number" placeholder="Net Sell"></td>
|
||||
<td><input name="split_pieces[0].net_sell" class="input" type="text" placeholder="Net Sell"></td>
|
||||
<td style="vertical-align: middle;"><a onclick="deleteRow(this)">删除</a></td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user