work
This commit is contained in:
parent
e8e8d9d960
commit
c012683af1
10 changed files with 384 additions and 30 deletions
19
Cargo.lock
generated
19
Cargo.lock
generated
|
@ -475,6 +475,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"actix-web",
|
||||
"env_logger",
|
||||
"futures",
|
||||
"log",
|
||||
"maud 0.25.0",
|
||||
"mdq",
|
||||
|
@ -848,6 +849,21 @@ version = "2.0.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c"
|
||||
|
||||
[[package]]
|
||||
name = "futures"
|
||||
version = "0.3.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.30"
|
||||
|
@ -855,6 +871,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -909,9 +926,11 @@ version = "0.3.30"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48"
|
||||
dependencies = [
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-macro",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
|
|
|
@ -6,6 +6,7 @@ edition = "2021"
|
|||
[dependencies]
|
||||
actix-web = "4.4.1"
|
||||
env_logger = "0.10.1"
|
||||
futures = "0.3.30"
|
||||
log = "0.4.20"
|
||||
maud = "0.25.0"
|
||||
mdq = { git = "https://git.hydrar.de/mdtools/mdq", version = "0.1.0" }
|
||||
|
|
10
README.md
Normal file
10
README.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
# CDB
|
||||
cdb is a powerful software tool designed to automate economic calculations by managing a database of items, their relations, quantities, and tracking demands. This tool facilitates the estimation of demand and supply, aiding in making informed decisions on production, imports, and purchases to meet market needs efficiently.
|
||||
|
||||
## Features
|
||||
- Data Input: Start with a root dataset of items and their relations, including details such as item names, categories, and dependencies.
|
||||
- Inventory Tracking: Record and update the quantities of items within the database based on real-world transactions, production, and consumption.
|
||||
- Demand Tracking: Monitor the demand for each item, keeping track of how many units are required over time.
|
||||
- Supply Calculation: Automatically calculate and update the estimated supply needed to meet the current demand.
|
||||
- Automated Economics: Utilize the collected data to automate economic decisions, determining optimal production levels, imports, and purchases to fulfill market needs.
|
||||
- Flexible Reporting: Generate reports and visualizations to analyze the economic trends
|
175
src/item.rs
175
src/item.rs
|
@ -1,22 +1,163 @@
|
|||
use mongodb::{Collection, bson::doc};
|
||||
use std::collections::HashSet;
|
||||
|
||||
use futures::TryStreamExt;
|
||||
use mongodb::{bson::doc, Collection};
|
||||
|
||||
macro_rules! collect_results {
|
||||
($res:expr) => {{
|
||||
let mut ret = vec![];
|
||||
|
||||
while let Some(doc) = $res.try_next().await.unwrap() {
|
||||
ret.push(doc);
|
||||
}
|
||||
|
||||
ret
|
||||
}};
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ItemVariantEntry {
|
||||
pub item_name: String,
|
||||
pub variant_name: String,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ItemInventoryEntry {
|
||||
pub item_name: String,
|
||||
pub variant_name: String,
|
||||
pub origin: String,
|
||||
pub price: f64,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, serde::Serialize)]
|
||||
pub struct ItemEntry {
|
||||
pub name: String,
|
||||
pub category: String
|
||||
pub category: String,
|
||||
}
|
||||
|
||||
impl ItemEntry {
|
||||
pub fn new(doc: mdq::Document) -> Self {
|
||||
let name = std::path::Path::new(&doc.path).file_stem().unwrap().to_str().unwrap().to_string();
|
||||
let category = doc.frontmatter.as_mapping().unwrap().get("category").unwrap().as_str().unwrap().to_string();
|
||||
let name = std::path::Path::new(&doc.path)
|
||||
.file_stem()
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
let category = doc
|
||||
.frontmatter
|
||||
.as_mapping()
|
||||
.unwrap()
|
||||
.get("category")
|
||||
.unwrap()
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.to_string();
|
||||
Self { name, category }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Item {
|
||||
db: mongodb::Client,
|
||||
pub item: ItemEntry,
|
||||
}
|
||||
|
||||
impl Item {
|
||||
pub fn new(db: mongodb::Client, item: ItemEntry) -> Self {
|
||||
Self { db, item }
|
||||
}
|
||||
|
||||
pub async fn get_inventory_entries(&self) -> Vec<ItemInventoryEntry> {
|
||||
let quantities: Collection<ItemInventoryEntry> =
|
||||
self.db.database("cdb").collection("item_quantities");
|
||||
|
||||
let mut found =
|
||||
quantities
|
||||
.find(doc! { "item_name": self.item.name.clone()}, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
collect_results!(found)
|
||||
}
|
||||
|
||||
pub async fn get_origins(&self) -> Vec<String> {
|
||||
let quantities: Collection<ItemInventoryEntry> =
|
||||
self.db.database("cdb").collection("item_quantities");
|
||||
|
||||
let mut found =
|
||||
quantities
|
||||
.find(doc! { "item_name": self.item.name.clone()}, None)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut hashset = HashSet::new();
|
||||
|
||||
while let Some(res) = found.try_next().await.unwrap() {
|
||||
hashset.insert(res.origin);
|
||||
}
|
||||
|
||||
hashset.into_iter().collect()
|
||||
}
|
||||
|
||||
pub async fn add_inventory(&self, variant: &str, origin: &str, price: f64) -> String {
|
||||
// todo : implement
|
||||
let quantities: Collection<ItemInventoryEntry> =
|
||||
self.db.database("cdb").collection("item_quantities");
|
||||
let id = quantities
|
||||
.insert_one(
|
||||
ItemInventoryEntry {
|
||||
item_name: self.item.name.clone(),
|
||||
variant_name: variant.to_owned(),
|
||||
origin: origin.to_owned(),
|
||||
price,
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
return id.inserted_id.as_object_id().unwrap().to_string();
|
||||
}
|
||||
|
||||
pub async fn get_variants(&self) -> Vec<String> {
|
||||
let variants: Collection<ItemVariantEntry> =
|
||||
self.db.database("cdb").collection("item_variants");
|
||||
let mut found_variants = variants
|
||||
.find(
|
||||
doc! {
|
||||
"item_name": self.item.name.clone()
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut ret = vec!["Generic".to_string()];
|
||||
|
||||
while let Some(doc) = found_variants.try_next().await.unwrap() {
|
||||
ret.push(doc.variant_name);
|
||||
}
|
||||
|
||||
ret
|
||||
}
|
||||
|
||||
pub async fn add_variant(&self, name: &str) {
|
||||
let variants: Collection<ItemVariantEntry> =
|
||||
self.db.database("cdb").collection("item_variants");
|
||||
variants
|
||||
.insert_one(
|
||||
ItemVariantEntry {
|
||||
item_name: self.item.name.clone(),
|
||||
variant_name: name.to_owned(),
|
||||
},
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ItemDB {
|
||||
index: mdq::Index,
|
||||
mongodb: mongodb::Client
|
||||
mongodb: mongodb::Client,
|
||||
}
|
||||
|
||||
impl ItemDB {
|
||||
|
@ -29,14 +170,30 @@ impl ItemDB {
|
|||
let item = ItemEntry::new(item.clone());
|
||||
log::info!("Adding item {} to DB", item.name);
|
||||
let items: Collection<ItemEntry> = mongodb.database("cdb").collection("items");
|
||||
items.insert_one(item, None).await.unwrap();
|
||||
if let None =
|
||||
items
|
||||
.find_one(doc! { "name": item.name.clone()}, None)
|
||||
.await
|
||||
.unwrap()
|
||||
{
|
||||
items.insert_one(item, None).await.unwrap();
|
||||
} else {
|
||||
// todo : update values
|
||||
}
|
||||
}
|
||||
|
||||
Self { index, mongodb }
|
||||
}
|
||||
|
||||
pub fn get_item(&self, item: &str) -> Option<ItemEntry> {
|
||||
self.index.documents.iter().map(|x| ItemEntry::new(x.clone())).find(|x| x.name == item)
|
||||
pub fn get_item(&self, item: &str) -> Option<Item> {
|
||||
Some(Item::new(
|
||||
self.mongodb.clone(),
|
||||
self.index
|
||||
.documents
|
||||
.iter()
|
||||
.map(|x| ItemEntry::new(x.clone()))
|
||||
.find(|x| x.name == item)?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn items(&self) -> Vec<String> {
|
||||
|
@ -47,4 +204,4 @@ impl ItemDB {
|
|||
}
|
||||
ret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
36
src/main.rs
36
src/main.rs
|
@ -1,14 +1,17 @@
|
|||
use actix_web::{get, HttpRequest, Responder};
|
||||
use maud::html;
|
||||
|
||||
mod item;
|
||||
mod pages;
|
||||
mod ui;
|
||||
|
||||
// ░░░░░░░░░░▀▀▀██████▄▄▄░░░░░░░░░░
|
||||
// ░░░░░░░░░░░░░░░░░▀▀▀████▄░░░░░░░
|
||||
// ░░░░░░░░░░▄███████▀░░░▀███▄░░░░░
|
||||
// ░░░░░░░░▄███████▀░░░░░░░▀███▄░░░
|
||||
// ░░░░░░▄████████░░░░░░░░░░░███▄░░
|
||||
// ░░░░░██████████▄░░░░░░░░░░░███▌░ ▒█▀▀█ █▀▀█ █▀▄▀█ █▀▄▀█ ▒█▀▀▄ ▒█▀▀█
|
||||
// ░░░░░▀█████▀░▀███▄░░░░░░░░░▐███░ ▒█░░░ █░░█ █░▀░█ █░▀░█ ▒█░▒█ ▒█▀▀▄
|
||||
// ░░░░░██████████▄░░░░░░░░░░░███▌░ ▒█▀▀█ █▀▀█ █▀▄▀█ █▀▄▀█ ▒█▀▀▄ ▒█▀▀█
|
||||
// ░░░░░▀█████▀░▀███▄░░░░░░░░░▐███░ ▒█░░░ █░░█ █░▀░█ █░▀░█ ▒█░▒█ ▒█▀▀▄
|
||||
// ░░░░░░░▀█▀░░░░░▀███▄░░░░░░░▐███░ ▒█▄▄█ ▀▀▀▀ ▀░░░▀ ▀░░░▀ ▒█▄▄▀ ▒█▄▄█
|
||||
// ░░░░░░░░░░░░░░░░░▀███▄░░░░░███▌░
|
||||
// ░░░░▄██▄░░░░░░░░░░░▀███▄░░▐███░░
|
||||
|
@ -17,23 +20,6 @@ mod item;
|
|||
// ░████▀░░░▀▀█████▄▄▄▄█████████▄░░
|
||||
// ░░▀▀░░░░░░░░░▀▀██████▀▀░░░▀▀██░░
|
||||
|
||||
#[get("/item/{item_id}")]
|
||||
pub async fn item_page(r: HttpRequest) -> impl Responder {
|
||||
let id = r.match_info().query("item_id");
|
||||
println!("{}", id);
|
||||
|
||||
let itemdb: &actix_web::web::Data<item::ItemDB> = r.app_data().unwrap();
|
||||
|
||||
let item = itemdb.get_item(id).unwrap();
|
||||
|
||||
let content = html!(
|
||||
p { "Item" };
|
||||
p { (format!("Category: {}", item.category))}
|
||||
).into_string();
|
||||
|
||||
web_base::build_site(&r, "Item", &content)
|
||||
}
|
||||
|
||||
#[get("/")]
|
||||
pub(crate) async fn index(r: HttpRequest) -> impl Responder {
|
||||
let itemdb: &actix_web::web::Data<item::ItemDB> = r.app_data().unwrap();
|
||||
|
@ -52,13 +38,21 @@ pub(crate) async fn index(r: HttpRequest) -> impl Responder {
|
|||
async fn main() -> std::io::Result<()> {
|
||||
env_logger::init();
|
||||
|
||||
let itemdb = item::ItemDB::new("./itemdb", "mongodb://user:pass@mongodb:27017").await;
|
||||
let itemdb = item::ItemDB::new("./itemdb", "mongodb://user:pass@127.0.0.1:27017").await;
|
||||
let itemdb = actix_web::web::Data::new(itemdb);
|
||||
|
||||
web_base::map!(
|
||||
web_base::Site::new()
|
||||
.head_content("<script src=\"https://cdn.tailwindcss.com\"></script>".to_string()),
|
||||
|app: actix_web::App<_>| { app.app_data(itemdb.clone()).service(index).service(item_page) }
|
||||
|app: actix_web::App<_>| {
|
||||
app.app_data(itemdb.clone())
|
||||
.service(index)
|
||||
.service(pages::item::item_page)
|
||||
.service(pages::item::variant_pages::add_item_variant_page)
|
||||
.service(pages::item::variant_pages::add_item_variant_page_post)
|
||||
.service(pages::item::inventory_page::add_item_inventory_page)
|
||||
.service(pages::item::inventory_page::add_item_inventory_page_post)
|
||||
}
|
||||
)
|
||||
.bind(("0.0.0.0".to_string(), 8080))?
|
||||
.run()
|
||||
|
|
70
src/pages/item/inventory_page.rs
Normal file
70
src/pages/item/inventory_page.rs
Normal file
|
@ -0,0 +1,70 @@
|
|||
use actix_web::{get, post, HttpRequest, Responder};
|
||||
use maud::html;
|
||||
|
||||
use crate::item;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct AddinventoryForm {
|
||||
variant: String,
|
||||
origin: String,
|
||||
price: f64,
|
||||
}
|
||||
|
||||
#[post("/item/{item_id}/inventory/add")]
|
||||
pub async fn add_item_inventory_page_post(
|
||||
r: HttpRequest,
|
||||
f: actix_web::web::Form<AddinventoryForm>,
|
||||
) -> impl Responder {
|
||||
let id = r.match_info().query("item_id");
|
||||
let itemdb: &actix_web::web::Data<item::ItemDB> = r.app_data().unwrap();
|
||||
let item = itemdb.get_item(id).unwrap();
|
||||
let rec_id = item.add_inventory(&f.variant, &f.origin, f.price).await;
|
||||
|
||||
// todo : generate receipts
|
||||
|
||||
web_base::redirect(&format!("/receipt/{}", rec_id))
|
||||
}
|
||||
|
||||
#[get("/item/{item_id}/inventory/add")]
|
||||
pub async fn add_item_inventory_page(r: HttpRequest) -> impl Responder {
|
||||
let id = r.match_info().query("item_id");
|
||||
|
||||
let itemdb: &actix_web::web::Data<item::ItemDB> = r.app_data().unwrap();
|
||||
|
||||
let item = itemdb.get_item(id).unwrap();
|
||||
let item_variants = item.get_variants().await;
|
||||
|
||||
// todo : get previous origins
|
||||
let item_origins = item.get_origins().await;
|
||||
|
||||
let content = html!(
|
||||
div class="bg-white p-8 rounded shadow-md max-w-md grid items-center justify-center m-auto mt-5" {
|
||||
h1 class="text-2xl font-bold mb-4" { "Add Item inventory" };
|
||||
form action=(format!("/item/{}/inventory/add", id)) method="post" {
|
||||
div {
|
||||
label class="text-sm font-medium text-gray-600 mr-3" for="variant" { "Variant:" };
|
||||
select class="mt-1 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500" id="variant" name="variant" {
|
||||
@for variant in item_variants {
|
||||
option value=(variant) { (variant) };
|
||||
};
|
||||
};
|
||||
};
|
||||
br;
|
||||
label class="text-sm font-medium text-gray-600" for="origin" { "Origin:" };
|
||||
input class="mt-1 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500" type="text" name="origin" list="origin_options" placeholder="Origin";
|
||||
datalist id="origin_options" {
|
||||
@for origin in item_origins {
|
||||
option value=(origin);
|
||||
}
|
||||
};
|
||||
br;
|
||||
label class="text-sm font-medium text-gray-600" for="price" { "Price: "};
|
||||
input class="mt-1 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500" type="number" step="0.01" name="price" placeholder="0.00";
|
||||
br;
|
||||
input class="hover:cursor-pointer bg-blue-500 text-white rounded px-4 py-2 mt-5" type="submit" name="submit" value="Add";
|
||||
};
|
||||
};
|
||||
).into_string();
|
||||
|
||||
web_base::build_site(&r, "Add Item inventory", &content)
|
||||
}
|
50
src/pages/item/mod.rs
Normal file
50
src/pages/item/mod.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
use actix_web::{get, HttpRequest, Responder};
|
||||
use maud::html;
|
||||
pub mod inventory_page;
|
||||
pub mod variant_pages;
|
||||
|
||||
use crate::{item, ui::button};
|
||||
|
||||
#[get("/item/{item_id}")]
|
||||
pub async fn item_page(r: HttpRequest) -> impl Responder {
|
||||
let id = r.match_info().query("item_id");
|
||||
println!("{}", id);
|
||||
|
||||
let itemdb: &actix_web::web::Data<item::ItemDB> = r.app_data().unwrap();
|
||||
|
||||
let item = itemdb.get_item(id).unwrap();
|
||||
|
||||
let content =
|
||||
html!(
|
||||
div class="bg-gray-100 min-h-screen min-w-screen items-center justify-center py-5" {
|
||||
div class="w-screen px-5" {
|
||||
div {
|
||||
p class="mb-2 font-bold text-2xl" { (format!("Item : {}", item.item.name)) };
|
||||
p class="font-bold" { (format!("Category: {}", item.item.category))};
|
||||
};
|
||||
div class="mt-5" {
|
||||
(button("Add Variant", &format!("/item/{}/variant/add", item.item.name)))
|
||||
(button("Add inventory", &format!("/item/{}/inventory/add", item.item.name)));
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
div class="mt-5 px-5" {
|
||||
h1 class="text-2xl font-bold mb-4" { "Variants" };
|
||||
@for variant in item.get_variants().await {
|
||||
p class="mb-2" { (variant) };
|
||||
}
|
||||
@for q in item.get_inventory_entries().await {
|
||||
div class="p-2" {
|
||||
p { (format!("Variant {}", q.variant_name))};
|
||||
p { (format!("Origin {}", q.origin))};
|
||||
p { (format!("Price {}", q.price))};
|
||||
};
|
||||
}
|
||||
};
|
||||
};
|
||||
)
|
||||
.into_string();
|
||||
|
||||
web_base::build_site(&r, "Item", &content)
|
||||
}
|
43
src/pages/item/variant_pages.rs
Normal file
43
src/pages/item/variant_pages.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
use actix_web::{get, post, HttpRequest, Responder};
|
||||
use maud::html;
|
||||
|
||||
use crate::item;
|
||||
|
||||
#[derive(serde::Serialize, serde::Deserialize)]
|
||||
struct AddVariantForm {
|
||||
variant_name: String,
|
||||
}
|
||||
|
||||
#[post("/item/{item_id}/variant/add")]
|
||||
pub async fn add_item_variant_page_post(
|
||||
r: HttpRequest,
|
||||
f: actix_web::web::Form<AddVariantForm>,
|
||||
) -> impl Responder {
|
||||
let id = r.match_info().query("item_id");
|
||||
let itemdb: &actix_web::web::Data<item::ItemDB> = r.app_data().unwrap();
|
||||
let item = itemdb.get_item(id).unwrap();
|
||||
item.add_variant(&f.variant_name).await;
|
||||
|
||||
web_base::redirect(&format!("/item/{}", id))
|
||||
}
|
||||
|
||||
#[get("/item/{item_id}/variant/add")]
|
||||
pub async fn add_item_variant_page(r: HttpRequest) -> impl Responder {
|
||||
let id = r.match_info().query("item_id");
|
||||
|
||||
let itemdb: &actix_web::web::Data<item::ItemDB> = r.app_data().unwrap();
|
||||
|
||||
let item = itemdb.get_item(id).unwrap();
|
||||
|
||||
let content = html!(
|
||||
div class="bg-white p-8 rounded shadow-md max-w-md grid items-center justify-center m-auto mt-5" {
|
||||
h1 class="text-2xl font-bold mb-4" { "Add Variant for " (item.item.name) };
|
||||
form action=(format!("/item/{}/variant/add", id)) method="post" {
|
||||
input class="mt-1 p-2 border border-gray-300 rounded focus:outline-none focus:border-blue-500" type="text" name="variant_name" placeholder="Name" { };
|
||||
input class="ml-5 hover:cursor-pointer bg-blue-500 text-white rounded px-4 py-2 mt-5" type="submit" name="submit" value="Add";
|
||||
};
|
||||
};
|
||||
).into_string();
|
||||
|
||||
web_base::build_site(&r, "Add Item Variant", &content)
|
||||
}
|
1
src/pages/mod.rs
Normal file
1
src/pages/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod item;
|
9
src/ui/mod.rs
Normal file
9
src/ui/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
use maud::html;
|
||||
|
||||
pub fn button(title: &str, redir: &str) -> maud::PreEscaped<String> {
|
||||
html!(
|
||||
a class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded hover:cursor-pointer mt-1 mr-1 mb-1" href=(redir) {
|
||||
(title)
|
||||
};
|
||||
)
|
||||
}
|
Loading…
Reference in a new issue