use crate::app::common::query_args::datetime_range::DatetimeRangeQueryArgs; 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, LocalTimestampRange, UtcTimestampRange}; use anyhow::Result; use askama::Template; use askama_axum::{IntoResponse, Response}; use axum::extract::State; use axum_htmx::HxRequest; use chrono::prelude::*; use serde::Deserialize; use sqlx::SqlitePool; use tracing::info; use crate::util::currency; #[derive(Template)] #[template(path = "history.html")] struct HistoryLogTemplate { items: Vec, start_date: String, start_time: String, end_date: String, end_time: String, time_zone: String, } #[derive(Template)] #[template(path = "history_item_fragment.html")] struct HistoryLogItemFragmentTemplate { items: Vec } #[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, } impl From for PositiveAdjustmentDisplayItem { fn from(adjustment: DbAdjustment) -> Self { Self { amount: format!("{}", adjustment.amount), unit_value: currency::int_cents_to_dollars_string(adjustment.unit_price.unwrap_or_default()), } } } #[derive(Clone, Debug)] struct NegativeAdjustmentDisplayItem { pub amount: String, pub reason: String, } impl From 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 { 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 }) } } pub async fn history_log_handler( QueryExtractor(mut query): QueryExtractor, HxRequest(hx_request): HxRequest, State(db): State, user: SessionUser ) -> Result { info!("Query: {:?}", query); let today = Local::now().naive_local().date(); let _ = query.time_zone_offset.get_or_insert(user.tz_offset); let _ = query.end_date.get_or_insert(today.to_string()); let local_range: LocalTimestampRange = query.try_into()?; let utc_range: UtcTimestampRange = local_range.into(); let tz_offset = local_range.start.timezone().local_minus_utc(); info!("Get items from: {} to {}", utc_range.start, utc_range.end); let items = DbAdjustmentWithUserAndItem::query_by_date_range( &db, utc_range.start, utc_range.end, 1000, 0) .await? .into_iter() .map(|x| { HistoryDisplayItem::from_adjustment(x, tz_offset) }) .collect::>>()?; info!("Item count: {}", items.len()); if hx_request { Ok(HistoryLogItemFragmentTemplate {items}.into_response()) } else { let time_zone = tz_offset_to_string(tz_offset); let start_date = local_range.start.naive_local().date().to_string(); let start_time = local_range.start.naive_local().time().to_string(); let end_date = local_range.end.naive_local().date().to_string(); let end_time = local_range.end.naive_local().time().to_string(); Ok(HistoryLogTemplate { items, start_date, start_time, end_date, end_time, time_zone, }.into_response()) } }