Lines
17.89 %
Functions
4.76 %
Branches
100 %
//web/src/pages/transaction/create/submit.rs
use askama::Template;
use axum::{
Extension, Json,
extract::{Query, State},
http::StatusCode,
response::IntoResponse,
};
use chrono::Local;
use serde::Deserialize;
use server::command::{CmdResult, FinanceEntity};
use sqlx::types::Uuid;
use std::sync::Arc;
use crate::pages::transaction::util::{
SplitData, parse_transaction_date, process_split_data, validate_splits_not_empty,
use crate::{AppState, jwt_auth::JWTAuthMiddleware, pages::HtmlTemplate};
#[derive(Deserialize)]
pub struct TransactionCreateParams {
from_account: Option<Uuid>,
}
#[derive(Template)]
#[template(path = "pages/transaction/create.html")]
struct TransactionCreatePage {
tags: Vec<finance::tag::Tag>,
transaction_id: Option<String>,
pub async fn transaction_create_page(
Query(params): Query<TransactionCreateParams>,
) -> impl IntoResponse {
let template = TransactionCreatePage {
from_account: params.from_account,
tags: Vec::new(),
transaction_id: None,
HtmlTemplate(template)
#[template(path = "components/transaction/create.html")]
struct TransactionFormTemplate {
pub async fn transaction_form() -> impl IntoResponse {
let template = TransactionFormTemplate {
#[derive(Deserialize, Debug)]
pub struct TransactionForm {
splits: Vec<SplitData>,
note: Option<String>,
date: Option<String>,
pub async fn transaction_submit(
State(_data): State<Arc<AppState>>,
Extension(jwt_auth): Extension<JWTAuthMiddleware>,
Json(form): Json<TransactionForm>,
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
let user = &jwt_auth.user;
// Validate splits
validate_splits_not_empty(&form.splits)?;
// Parse date
let post_date = parse_transaction_date(form.date.as_deref());
let post_date_utc = post_date.and_utc();
let enter_date_utc = Local::now().naive_utc().and_utc();
// Create transaction ID
let tx_id = Uuid::new_v4();
// Process splits using shared utility
let mut split_entities = Vec::new();
let mut prices = Vec::new();
let mut split_tags_to_create = Vec::new();
for split_data in form.splits {
let processed = process_split_data(user.id, tx_id, split_data).await?;
let from_split_id = processed.from_split.id;
let to_split_id = processed.to_split.id;
split_entities.push(FinanceEntity::Split(processed.from_split));
split_entities.push(FinanceEntity::Split(processed.to_split));
if let Some(price) = processed.price {
prices.push(FinanceEntity::Price(price));
if let Some(tags) = processed.from_split_tags {
for tag in tags {
split_tags_to_create.push((from_split_id, tag));
if let Some(tags) = processed.to_split_tags {
split_tags_to_create.push((to_split_id, tag));
// Execute command
let mut cmd = server::command::transaction::CreateTransaction::new()
.user_id(user.id)
.splits(split_entities)
.id(tx_id)
.post_date(post_date_utc)
.enter_date(enter_date_utc);
if !prices.is_empty() {
cmd = cmd.prices(prices);
if let Some(note) = form.note.as_deref()
&& !note.trim().is_empty()
{
cmd = cmd.note(note.to_string());
match cmd.run().await {
Ok(result) => {
// Create split tags after transaction is successfully created
let server_user = server::user::User { id: user.id };
for (split_id, tag_data) in split_tags_to_create {
server_user
.create_split_tag(
split_id,
tag_data.name,
tag_data.value,
tag_data.description,
)
.await
.map_err(|e| {
let error_response = serde_json::json!({
"status": "fail",
"message": format!("Failed to create split tag: {:?}", e),
});
log::error!("Failed to create split tag for split {}: {:?}", split_id, e);
(StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
})?;
match result {
Some(CmdResult::Entity(FinanceEntity::Transaction(tx))) => Ok(format!(
"{}: {}",
t!("New transaction created with ID"),
tx.id
)),
_ => Ok(t!("New transaction created successfully").to_string()),
Err(e) => {
"message": format!("Failed to create transaction: {:?}", e),
log::error!("Failed to create transaction: {e:?}");
Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)))