Merge adjustment tables into one

demo-mode
Wes Holland 1 year ago
parent 74342dcc82
commit 5fd8e249b1

@ -2,6 +2,7 @@
CREATE TABLE IF NOT EXISTS User ( CREATE TABLE IF NOT EXISTS User (
id INTEGER PRIMARY KEY NOT NULL, id INTEGER PRIMARY KEY NOT NULL,
email TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
role INTEGER NOT NULL, role INTEGER NOT NULL,
tz_offset INTEGER NOT NULL, tz_offset INTEGER NOT NULL,
@ -27,19 +28,7 @@ CREATE TABLE IF NOT EXISTS InventoryItem (
FOREIGN KEY(display_unit) REFERENCES DisplayUnit(id) FOREIGN KEY(display_unit) REFERENCES DisplayUnit(id)
); );
CREATE TABLE IF NOT EXISTS PositiveAdjustment ( CREATE TABLE IF NOT EXISTS Adjustment (
id INTEGER PRIMARY KEY NOT NULL,
item INTEGER NOT NULL,
user INTEGER NOT NULL,
create_date DATETIME NOT NULL,
target_date DATETIME NOT NULL,
amount REAL NOT NULL,
unit_price INTEGER NOT NULL,
FOREIGN KEY(user) REFERENCES User(id),
FOREIGN KEY(item) REFERENCES InventoryItem(id)
);
CREATE TABLE IF NOT EXISTS NegativeAdjustment (
id INTEGER PRIMARY KEY NOT NULL, id INTEGER PRIMARY KEY NOT NULL,
item INTEGER NOT NULL, item INTEGER NOT NULL,
user INTEGER NOT NULL, user INTEGER NOT NULL,
@ -47,22 +36,24 @@ CREATE TABLE IF NOT EXISTS NegativeAdjustment (
target_date INTEGER NOT NULL, target_date INTEGER NOT NULL,
amount REAL NOT NULL, amount REAL NOT NULL,
reason INTEGER NOT NULL, reason INTEGER NOT NULL,
unit_price INTEGER,
FOREIGN KEY(user) REFERENCES User(id), FOREIGN KEY(user) REFERENCES User(id),
FOREIGN KEY(item) REFERENCES InventoryItem(id), FOREIGN KEY(item) REFERENCES InventoryItem(id),
FOREIGN KEY(reason) REFERENCES NegativeAdjustmentReason(id) FOREIGN KEY(reason) REFERENCES AdjustmentReason(id)
); );
CREATE TABLE IF NOT EXISTS NegativeAdjustmentReason ( CREATE TABLE IF NOT EXISTS AdjustmentReason (
id INTEGER PRIMARY KEY NOT NULL, id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL name TEXT NOT NULL
); );
INSERT INTO NegativeAdjustmentReason (id, name) VALUES INSERT INTO AdjustmentReason (id, name) VALUES
(0,'unknown'), (0,'unknown'),
(10,'sale'), (10,'sale'),
(20,'destruction'), (20,'destruction'),
(25,'expiration'), (25,'expiration'),
(30,'theft'); (30,'theft'),
(50,'new-stock');
CREATE TABLE IF NOT EXISTS DisplayUnit ( CREATE TABLE IF NOT EXISTS DisplayUnit (
id INTEGER PRIMARY KEY NOT NULL, id INTEGER PRIMARY KEY NOT NULL,

@ -1,20 +1,21 @@
use crate::db::adjustment::{get_adjustments_target_date_range, DbAdjustment, DbAdjustmentWithUserAndItem};
use crate::error::{AppError, QueryExtractor};
use crate::session::SessionUser;
use crate::util::time::tz_offset_to_string;
use anyhow::Result;
use askama::Template; use askama::Template;
use askama_axum::{IntoResponse, Response}; use askama_axum::{IntoResponse, Response};
use axum::extract::State; use axum::extract::State;
use axum_htmx::HxRequest; use axum_htmx::HxRequest;
use chrono::prelude::*;
use serde::Deserialize; use serde::Deserialize;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tracing::info; use tracing::info;
use chrono::prelude::*;
use crate::db::positive_adjustment::{get_positive_adjustments_target_date_range, DbPositiveAdjustment};
use crate::error::{AppError, QueryExtractor};
use crate::session::SessionUser;
use crate::util::time::tz_offset_to_string;
#[derive(Template)] #[derive(Template)]
#[template(path = "history.html")] #[template(path = "history.html")]
struct HistoryLogTemplate { struct HistoryLogTemplate {
items: Vec<DbPositiveAdjustment>, items: Vec<HistoryDisplayItem>,
start_date: String, start_date: String,
start_time: String, start_time: String,
end_date: String, end_date: String,
@ -25,9 +26,94 @@ struct HistoryLogTemplate {
#[derive(Template)] #[derive(Template)]
#[template(path = "history_item_fragment.html")] #[template(path = "history_item_fragment.html")]
struct HistoryLogItemFragmentTemplate { struct HistoryLogItemFragmentTemplate {
items: Vec<DbPositiveAdjustment> items: Vec<HistoryDisplayItem>
}
#[derive(Clone, Debug)]
struct HistoryDisplayItem {
pub create_date: String,
pub target_date: String,
pub user_name: String,
pub item_name: String,
pub item_unit: String,
pub item_unit_abbreviation: String,
pub item_type: HistoryItemEntry,
}
#[derive(Clone, Debug)]
struct PositiveAdjustmentDisplayItem {
pub amount: String,
pub unit_value: String,
}
pub fn int_cents_to_currency_string(i: i64) -> String {
let whole = i / 100;
let cents = i % 100;
format!("${}.{}", whole, cents)
}
impl From<DbAdjustment> for PositiveAdjustmentDisplayItem {
fn from(adjustment: DbAdjustment) -> Self {
Self {
amount: format!("{}", adjustment.amount),
unit_value: int_cents_to_currency_string(adjustment.unit_price.unwrap_or_default()),
}
}
} }
#[derive(Clone, Debug)]
struct NegativeAdjustmentDisplayItem {
pub amount: String,
pub reason: String,
}
impl From<DbAdjustment> for NegativeAdjustmentDisplayItem {
fn from(adjustment: DbAdjustment) -> Self {
Self {
amount: format!("{}", adjustment.amount),
reason: adjustment.reason.into(),
}
}
}
#[derive(Clone, Debug)]
enum HistoryItemEntry {
PositiveAdjustment(PositiveAdjustmentDisplayItem),
NegativeAdjustment(NegativeAdjustmentDisplayItem),
}
impl HistoryDisplayItem {
pub fn from_adjustment(db_entry: DbAdjustmentWithUserAndItem, tz_offset: i32)
-> Result<Self> {
let simple_entry: DbAdjustment = db_entry.clone().into();
let timezone = FixedOffset::east_opt(tz_offset)
.ok_or(anyhow::anyhow!("Invalid timezone"))?;
let create_date = db_entry.create_date.with_timezone(&timezone)
.format("%Y-%m-%d %l:%M:%S %p").to_string();
let target_date = db_entry.target_date.with_timezone(&timezone)
.format("%Y-%m-%d %l:%M:%S %p").to_string();
let item_type = if simple_entry.amount > 0.0 {
HistoryItemEntry::PositiveAdjustment(simple_entry.into())
} else {
HistoryItemEntry::NegativeAdjustment(simple_entry.into())
};
Ok(Self {
create_date,
target_date,
user_name: db_entry.user_name,
item_name: db_entry.item_name,
item_unit: db_entry.item_unit,
item_unit_abbreviation: db_entry.item_unit_abbreviation,
item_type
})
}
}
/// Common query args for datetime ranges /// Common query args for datetime ranges
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
pub struct DatetimeRangeQueryArgs { pub struct DatetimeRangeQueryArgs {
@ -50,12 +136,14 @@ pub async fn history_log_handler(
user: SessionUser user: SessionUser
) -> Result<Response, AppError> { ) -> Result<Response, AppError> {
info!("Query: {:?}", query);
let today = Local::now().naive_local().date(); let today = Local::now().naive_local().date();
let start_date = query.start_date.unwrap_or("2000-01-01".to_string()); let start_date = query.start_date.unwrap_or("2000-01-01".to_string());
let start_time = query.start_time.unwrap_or("00:00:00".to_string()); let start_time = query.start_time.unwrap_or("00:00:00".to_string());
let end_date = query.end_date.unwrap_or(today.to_string()); let end_date = query.end_date.unwrap_or(today.to_string());
let end_time = query.end_time.unwrap_or("11:59:59".to_string()); let end_time = query.end_time.unwrap_or("23:59:59".to_string());
let tz_offset = query.time_zone_offset.unwrap_or(user.tz_offset); let tz_offset = query.time_zone_offset.unwrap_or(user.tz_offset);
let timezone = FixedOffset::east_opt(tz_offset) let timezone = FixedOffset::east_opt(tz_offset)
.ok_or(anyhow::anyhow!("Invalid timezone"))?; .ok_or(anyhow::anyhow!("Invalid timezone"))?;
@ -65,25 +153,38 @@ pub async fn history_log_handler(
let naive_end_date = end_date.parse::<NaiveDate>()?; let naive_end_date = end_date.parse::<NaiveDate>()?;
let naive_end_time = end_time.parse::<NaiveTime>()?; let naive_end_time = end_time.parse::<NaiveTime>()?;
let combined_start = naive_start_date
let local_start = naive_start_date
.and_time(naive_start_time) .and_time(naive_start_time)
.and_local_timezone(timezone) .and_local_timezone(timezone)
.earliest() .earliest()
.ok_or(anyhow::anyhow!("Invalid start"))? .ok_or(anyhow::anyhow!("Invalid start"))?;
.to_utc();
let combined_end = naive_end_date let combined_start = local_start.to_utc();
let local_end = naive_end_date
.and_time(naive_end_time) .and_time(naive_end_time)
.and_local_timezone(timezone) .and_local_timezone(timezone)
.latest() .latest()
.ok_or(anyhow::anyhow!("Invalid start"))? .ok_or(anyhow::anyhow!("Invalid start"))?;
.to_utc();
let combined_end = local_end.to_utc();
let start_date = local_start.naive_local().date().to_string();
let start_time = local_start.naive_local().time().to_string();
let end_date = local_end.naive_local().date().to_string();
let end_time = local_end.naive_local().time().to_string();
info!("Get items from: {} to {}", combined_start, combined_end); info!("Get items from: {} to {}", combined_start, combined_end);
let items = get_positive_adjustments_target_date_range(&db, let items = DbAdjustmentWithUserAndItem::query_by_date_range(
combined_start, combined_end).await?; &db, combined_start, combined_end, 1000, 0)
.await?
.into_iter()
.map(|x| {
HistoryDisplayItem::from_adjustment(x, tz_offset)
})
.collect::<Result<Vec<HistoryDisplayItem>>>()?;
info!("Item count: {}", items.len()); info!("Item count: {}", items.len());

@ -135,7 +135,7 @@ pub async fn auth_authorized(
.context("failed to deserialize response as JSON")?; .context("failed to deserialize response as JSON")?;
// STEP 7 - Authorize the user at the application level // STEP 7 - Authorize the user at the application level
let db_user = match db::user::get_user_by_name(&db, &oauth_user_data.email).await? { let db_user = match db::user::get_user_by_email(&db, &oauth_user_data.email).await? {
Some(user) => user, Some(user) => user,
None => { None => {
return Ok(AppForbiddenResponse::new(&oauth_user_data.email, "application").into_response()) return Ok(AppForbiddenResponse::new(&oauth_user_data.email, "application").into_response())

@ -0,0 +1,206 @@
use serde::Serialize;
use anyhow::Result;
use chrono::{DateTime, Utc};
use sqlx::SqlitePool;
use tracing::error;
#[derive(Clone, Debug, Serialize)]
#[derive(sqlx::FromRow)]
pub struct DbAdjustment {
pub id: i64,
pub item: i64,
pub user: i64,
pub create_date: DateTime<Utc>,
pub target_date: DateTime<Utc>,
pub amount: f64,
pub unit_price: Option<i64>,
#[sqlx(try_from = "i64")]
pub reason: DbAdjustmentReason,
}
pub async fn add_new_stock_adjustment(db: &SqlitePool, item: i64, user: i64,
create_date: DateTime<Utc>, target_date: DateTime<Utc>,
amount: f64, unit_price: i64)
-> Result<i64> {
let reason: i64 = DbAdjustmentReason::NewStock.into();
let res = sqlx::query!(
r#"
INSERT INTO Adjustment (
item,
user,
create_date,
target_date,
amount,
reason,
unit_price)
VALUES (?, ?, ?, ?, ?, ?, ?)
"#,
item, user, create_date, target_date, amount, reason, unit_price
).execute(db).await?;
let new_id = res.last_insert_rowid();
Ok(new_id)
}
pub async fn get_adjustments_target_date_range(
db: &SqlitePool, start_date: DateTime<Utc>, end_date: DateTime<Utc>
) -> Result<Vec<DbAdjustment>> {
sqlx::query_as::<_, DbAdjustment>(r#"
SELECT id, item, user,
create_date,
target_date,
amount,
reason,
unit_price
FROM Adjustment
WHERE target_date >= ? AND target_date <= ?
"#,)
.bind(start_date)
.bind(end_date)
.fetch_all(db)
.await.map_err(Into::into)
}
#[derive(Clone, Debug, Serialize)]
#[derive(sqlx::FromRow)]
pub struct DbAdjustmentWithUserAndItem {
pub id: i64,
pub item: i64,
pub item_name: String,
pub item_unit: String,
pub item_unit_abbreviation: String,
pub user: i64,
pub user_name: String,
pub create_date: DateTime<Utc>,
pub target_date: DateTime<Utc>,
pub amount: f64,
pub unit_price: Option<i64>,
#[sqlx(try_from = "i64")]
pub reason: DbAdjustmentReason,
}
impl DbAdjustmentWithUserAndItem {
pub async fn query_by_date_range(db: &SqlitePool,
start_date: DateTime<Utc>,
end_date: DateTime<Utc>,
page_size: i64,
page_num: i64) -> Result<Vec<Self>> {
let offset = page_size * page_num;
sqlx::query_as::<_, DbAdjustmentWithUserAndItem>(r#"
SELECT
adj.id as id,
adj.item as item,
item.name as item_name,
unit.unit as item_unit,
unit.abbreviation as item_unit_abbreviation,
adj.user as user,
user.name as user_name,
adj.create_date as create_date,
adj.target_date as target_date,
adj.amount as amount,
adj.unit_price as unit_price,
adj.reason as reason
FROM Adjustment as adj
JOIN User as user on adj.user = user.id
JOIN InventoryItem as item on adj.item = item.id
JOIN DisplayUnit as unit on item.display_unit = unit.id
WHERE adj.target_date >= ? AND adj.target_date <= ?
LIMIT ? OFFSET ?
"#,)
.bind(start_date)
.bind(end_date)
.bind(page_size)
.bind(offset)
.fetch_all(db)
.await.map_err(Into::into)
}
}
impl Into<DbAdjustment> for DbAdjustmentWithUserAndItem {
fn into(self) -> DbAdjustment {
DbAdjustment {
id: self.id,
item: self.item,
user: self.user,
create_date: self.create_date,
target_date: self.target_date,
amount: self.amount,
unit_price: self.unit_price,
reason: self.reason,
}
}
}
#[derive(Debug, Clone, Copy, Serialize)]
pub enum DbAdjustmentReason {
Unknown,
Sale,
Destruction,
Expiration,
Theft,
NewStock,
}
impl Into<i64> for DbAdjustmentReason {
fn into(self) -> i64 {
match self {
Self::Unknown => 0,
Self::Sale => 10,
Self::Destruction => 20,
Self::Expiration => 25,
Self::Theft => 30,
Self::NewStock => 50,
}
}
}
impl From<i64> for DbAdjustmentReason {
fn from(item: i64) -> Self {
match item {
0 => Self::Unknown,
10 => Self::Sale,
20 => Self::Destruction,
25 => Self::Expiration,
30 => Self::Theft,
50 => Self::NewStock,
_ => {
error!("unknown negative adjustment reason value: {}", item);
Self::Unknown
}
}
}
}
impl TryFrom<&str> for DbAdjustmentReason {
type Error = anyhow::Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
match value {
"unknown" => Ok(Self::Unknown),
"sale" => Ok(Self::Sale),
"destruction" => Ok(Self::Destruction),
"expiration" => Ok(Self::Expiration),
"theft" => Ok(Self::Theft),
"new-stock" => Ok(Self::NewStock),
_ => Err(anyhow::anyhow!("unknown negative adjustment reason"))
}
}
}
impl Into<String> for DbAdjustmentReason {
fn into(self) -> String {
match self {
Self::Unknown => { String::from("unknown") }
Self::Sale => { String::from("sale") }
Self::Destruction => { String::from("destruction") }
Self::Expiration => { String::from("expiration") }
Self::Theft => { String::from("theft") }
Self::NewStock => { String::from("new-stock") }
}
}
}

@ -1,5 +1,5 @@
use std::str::FromStr; use std::str::FromStr;
use crate::db::user::{add_user, get_user_by_name, get_user_count, DbUserRole}; use crate::db::user::{add_user, get_user_by_email, get_user_count, DbUserRole};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use serde::Deserialize; use serde::Deserialize;
use sqlx::SqlitePool; use sqlx::SqlitePool;
@ -14,6 +14,7 @@ struct BootstrapData {
#[derive(Debug, Deserialize)] #[derive(Debug, Deserialize)]
struct BootstrapUser { struct BootstrapUser {
email: String,
name: String, name: String,
role: String, role: String,
tz_offset: Option<String>, tz_offset: Option<String>,
@ -40,8 +41,8 @@ pub async fn bootstrap_database(db: &SqlitePool) -> Result<()> {
let tz = if let Some(tz) = &user.tz_offset { let tz = if let Some(tz) = &user.tz_offset {
i64::from_str(&tz).unwrap_or(DEFAULT_TIMEZONE) i64::from_str(&tz).unwrap_or(DEFAULT_TIMEZONE)
} else { DEFAULT_TIMEZONE }; } else { DEFAULT_TIMEZONE };
if get_user_by_name(db, &user.name).await?.is_none() { if get_user_by_email(db, &user.name).await?.is_none() {
let new_id = add_user(db, &user.name, role, tz).await?; let new_id = add_user(db, &user.email, &user.name, role, tz).await?;
info!("bootstrap new user {}:{} ({})", new_id, user.name, user.role); info!("bootstrap new user {}:{} ({})", new_id, user.name, user.role);
} }
} }

@ -85,18 +85,15 @@ pub async fn inventory_item_get_by_id(db: &SqlitePool, id: i64) -> Result<DbInve
pub async fn sum_all_adjustments_for_item(db: &SqlitePool, id: i64) -> Result<f64> { pub async fn sum_all_adjustments_for_item(db: &SqlitePool, id: i64) -> Result<f64> {
let res = sqlx::query!( let res = sqlx::query!(
r#" r#"
SELECT SELECT TOTAL(amount) as amt FROM Adjustment WHERE item = ?
(SELECT TOTAL(amount) FROM PositiveAdjustment WHERE item = ?) AS plus,
(SELECT TOTAL(amount) FROM NegativeAdjustment WHERE item = ?) AS minus
"#, "#,
id, id id
) )
.fetch_one(db).await?; .fetch_one(db).await?;
let plus: f64 = res.plus.unwrap_or_default(); let amt: f64 = res.amt.unwrap_or_default();
let minus: f64 = res.minus.unwrap_or_default();
Ok(amt)
Ok(plus - minus)
} }
pub async fn add_inventory_item(db: &SqlitePool, name: &str, reorder_point: f64, pub async fn add_inventory_item(db: &SqlitePool, name: &str, reorder_point: f64,

@ -1,8 +1,7 @@
pub mod inventory_item; pub mod inventory_item;
pub mod positive_adjustment; pub mod adjustment;
mod bootstrap_data; mod bootstrap_data;
pub mod user; pub mod user;
pub mod negative_adjustment;
use crate::db::bootstrap_data::bootstrap_database; use crate::db::bootstrap_data::bootstrap_database;
use anyhow::Context; use anyhow::Context;

@ -1,115 +0,0 @@
use serde::Serialize;
use sqlx::SqlitePool;
use anyhow::Result;
use chrono::{DateTime, Utc};
use tracing::error;
#[derive(Debug, Serialize)]
#[derive(sqlx::FromRow)]
pub struct DbNegativeAdjustment {
pub id: i64,
pub item: i64,
pub user: i64,
pub create_date: i64,
pub target_date: i64,
pub amount: f64,
pub reason: DbNegativeAdjustmentReason,
}
pub async fn add_negative_adjustment(db: &SqlitePool, item: i64, user: i64,
create_date: DateTime<Utc>, target_date: DateTime<Utc>,
amount: f64, reason: DbNegativeAdjustmentReason) -> Result<i64> {
let reason: i64 = reason.into();
let res = sqlx::query!(
r#"
INSERT INTO NegativeAdjustment (item, user, create_date, target_date, amount, reason)
VALUES (?, ?, ?, ?, ?, ?)
"#,
item, user, create_date, target_date, amount, reason
).execute(db).await?;
let new_id = res.last_insert_rowid();
Ok(new_id)
}
pub async fn get_negative_adjustments_target_date_range(
db: &SqlitePool, start_date: DateTime<Utc>, end_date: DateTime<Utc>
) -> Result<Vec<DbNegativeAdjustment>> {
sqlx::query_as!(
DbNegativeAdjustment,
r#"
SELECT id, item, user, create_date, target_date, amount, reason
FROM NegativeAdjustment
WHERE target_date >= ? AND target_date <= ?
"#,
start_date, end_date
)
.fetch_all(db)
.await.map_err(Into::into)
}
#[derive(Debug, Clone, Copy, Serialize)]
pub enum DbNegativeAdjustmentReason {
Unknown,
Sale,
Destruction,
Expiration,
Theft,
}
impl Into<i64> for DbNegativeAdjustmentReason {
fn into(self) -> i64 {
match self {
Self::Unknown => 0,
Self::Sale => 10,
Self::Destruction => 20,
Self::Expiration => 25,
Self::Theft => 30,
}
}
}
impl From<i64> for DbNegativeAdjustmentReason {
fn from(item: i64) -> Self {
match item {
0 => Self::Unknown,
10 => Self::Sale,
20 => Self::Destruction,
25 => Self::Expiration,
30 => Self::Theft,
_ => {
error!("unknown negative adjustment reason value: {}", item);
Self::Unknown
}
}
}
}
impl TryFrom<&str> for DbNegativeAdjustmentReason {
type Error = anyhow::Error;
fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
match value {
"unknown" => Ok(Self::Unknown),
"sale" => Ok(Self::Sale),
"destruction" => Ok(Self::Destruction),
"expiration" => Ok(Self::Expiration),
"theft" => Ok(Self::Theft),
_ => Err(anyhow::anyhow!("unknown negative adjustment reason"))
}
}
}
impl Into<String> for DbNegativeAdjustmentReason {
fn into(self) -> String {
match self {
Self::Unknown => { String::from("unknown") }
Self::Sale => { String::from("sale") }
Self::Destruction => { String::from("destruction") }
Self::Expiration => { String::from("expiration") }
Self::Theft => { String::from("theft") }
}
}
}

@ -1,53 +0,0 @@
use serde::Serialize;
use anyhow::Result;
use chrono::{DateTime, Utc};
use sqlx::SqlitePool;
#[derive(Debug, Serialize)]
#[derive(sqlx::FromRow)]
pub struct DbPositiveAdjustment {
pub id: i64,
pub item: i64,
pub user: i64,
pub create_date: DateTime<Utc>,
pub target_date: DateTime<Utc>,
pub amount: f64,
pub unit_price: i64,
}
pub async fn add_positive_adjustment(db: &SqlitePool, item: i64, user: i64,
create_date: DateTime<Utc>, target_date: DateTime<Utc>,
amount: f64, unit_price: i64)
-> Result<i64> {
let res = sqlx::query!(
r#"
INSERT INTO PositiveAdjustment (item, user, create_date, target_date, amount, unit_price)
VALUES (?, ?, ?, ?, ?, ?)
"#,
item, user, create_date, target_date, amount, unit_price
).execute(db).await?;
let new_id = res.last_insert_rowid();
Ok(new_id)
}
pub async fn get_positive_adjustments_target_date_range(
db: &SqlitePool, start_date: DateTime<Utc>, end_date: DateTime<Utc>
) -> Result<Vec<DbPositiveAdjustment>> {
sqlx::query_as::<_, DbPositiveAdjustment>(r#"
SELECT id, item, user,
create_date,
target_date,
amount, unit_price
FROM PositiveAdjustment
WHERE target_date >= ? AND target_date <= ?
"#,)
.bind(start_date)
.bind(end_date)
.fetch_all(db)
.await.map_err(Into::into)
}

@ -6,36 +6,39 @@ use sqlx::SqlitePool;
#[derive(sqlx::FromRow)] #[derive(sqlx::FromRow)]
pub struct DbUser { pub struct DbUser {
pub id: i64, pub id: i64,
pub email: String,
pub name: String, pub name: String,
pub role: i64, pub role: i64,
pub tz_offset: i64, pub tz_offset: i64,
} }
pub async fn get_user_by_name(db: &SqlitePool, user: &str) -> Result<Option<DbUser>> { pub async fn get_user_by_email(db: &SqlitePool, email: &str) -> Result<Option<DbUser>> {
sqlx::query_as::<_, DbUser>( sqlx::query_as::<_, DbUser>(
r#" r#"
SELECT SELECT
id, id,
name, email,
name,
role, role,
tz_offset tz_offset
FROM FROM
User User
WHERE WHERE
name = ? email = ?
"#) "#)
.bind(user) .bind(email)
.fetch_optional(db).await .fetch_optional(db).await
.map_err(From::from) .map_err(From::from)
} }
pub async fn add_user(db: &SqlitePool, name: &str, role: DbUserRole, tz_offset: i64) -> Result<i64> { pub async fn add_user(db: &SqlitePool, email: &str, name: &str, role: DbUserRole, tz_offset: i64) -> Result<i64> {
let role = role.value(); let role = role.value();
let res = sqlx::query( let res = sqlx::query(
r#" r#"
INSERT INTO User(name, role, tz_offset) INSERT INTO User(email, name, role, tz_offset)
VALUES (?, ?, ?) VALUES (?, ?, ?, ?)
"#) "#)
.bind(email)
.bind(name) .bind(name)
.bind(role) .bind(role)
.bind(tz_offset) .bind(tz_offset)

@ -4,7 +4,7 @@ use axum::body::Bytes;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tracing::info; use tracing::info;
use crate::db::inventory_item::add_inventory_item; use crate::db::inventory_item::add_inventory_item;
use crate::db::positive_adjustment::add_positive_adjustment; use crate::db::adjustment::add_new_stock_adjustment;
#[derive(Debug, serde::Deserialize)] #[derive(Debug, serde::Deserialize)]
struct CatalogRecord { struct CatalogRecord {
@ -38,8 +38,8 @@ pub async fn ingest_catalog<T: std::io::Read>(mut reader: csv::Reader<T>, db: Sq
record.fractional, record.fractional,
&record.unit).await?; &record.unit).await?;
let new_positive_adjustment = add_positive_adjustment(&db, new_entry_id, let new_positive_adjustment = add_new_stock_adjustment(&db, new_entry_id,
user_id, timestamp, timestamp, record.quantity, record.unit_price).await?; user_id, timestamp, timestamp, record.quantity, record.unit_price).await?;
info!("Added new item: {}/{} - {}", new_entry_id, new_positive_adjustment, record.name); info!("Added new item: {}/{} - {}", new_entry_id, new_positive_adjustment, record.name);
} }

@ -10,11 +10,11 @@
<div class="grid"> <div class="grid">
<fieldset> <fieldset>
<label for="start-date">Start Date</label> <label for="start-date">Start Date</label>
<input type="date" id="start-date" name="start" value="{{ start_date }}" /> <input type="date" id="start-date" name="start-date" value="{{ start_date }}" />
</fieldset> </fieldset>
<fieldset> <fieldset>
<label for="start-time">Start Time</label> <label for="start-time">Start Time</label>
<input type="time" id="start-time" name="start" value="{{ start_time }}"/> <input type="time" id="start-time" name="start-time" value="{{ start_time }}"/>
<small>Timezone {{ time_zone }}</small> <small>Timezone {{ time_zone }}</small>
</fieldset> </fieldset>
</div> </div>
@ -22,11 +22,11 @@
<div class="grid"> <div class="grid">
<fieldset> <fieldset>
<label for="end-date">End Date</label> <label for="end-date">End Date</label>
<input type="date" id="end-date" name="end" value="{{ end_date }}"/> <input type="date" id="end-date" name="end-date" value="{{ end_date }}"/>
</fieldset> </fieldset>
<fieldset> <fieldset>
<label for="end-time">End Time</label> <label for="end-time">End Time</label>
<input type="time" id="end-time" name="end" value="{{ end_time }}"/> <input type="time" id="end-time" name="end-time" value="{{ end_time }}"/>
<small>Timezone {{ time_zone }}</small> <small>Timezone {{ time_zone }}</small>
</fieldset> </fieldset>
</div> </div>

@ -1,9 +1,18 @@
{% for item in items %} {% for item in items %}
<article id="item-{{item.id}}-card"> <article>
<div class="grid"> <div class="grid">
<p>{{ item.item }}</p> <p>{{ item.create_date }}</p>
<p>{{ item.amount }}</p> <p>{{ item.item_name }}</p>
<p>{{ item.user_name }}</p>
<p>
{% match item.item_type %}
{% when HistoryItemEntry::PositiveAdjustment with (entry) %}
{{ entry.amount }} @ {{ entry.unit_value }}
{% when HistoryItemEntry::NegativeAdjustment with (entry) %}
{{ entry.amount }} {{ entry.reason }}
{% endmatch %}
</p>
</div> </div>
</article> </article>
{% endfor %} {% endfor %}
Loading…
Cancel
Save

Powered by TurnKey Linux.