1
//web/src/pages/transaction/create/submit.rs
2

            
3
use askama::Template;
4
use axum::{
5
    Extension, Json,
6
    extract::{Query, State},
7
    http::StatusCode,
8
    response::IntoResponse,
9
};
10
use chrono::Local;
11
use serde::Deserialize;
12
use server::command::{CmdResult, FinanceEntity};
13
use sqlx::types::Uuid;
14
use std::sync::Arc;
15

            
16
use crate::pages::transaction::util::{
17
    SplitData, parse_transaction_date, process_split_data, validate_splits_not_empty,
18
};
19
use crate::{AppState, jwt_auth::JWTAuthMiddleware, pages::HtmlTemplate};
20

            
21
#[derive(Deserialize)]
22
pub struct TransactionCreateParams {
23
    from_account: Option<Uuid>,
24
}
25

            
26
#[derive(Template)]
27
#[template(path = "pages/transaction/create.html")]
28
struct TransactionCreatePage {
29
    from_account: Option<Uuid>,
30
    tags: Vec<finance::tag::Tag>,
31
    transaction_id: Option<String>,
32
}
33

            
34
pub async fn transaction_create_page(
35
    Query(params): Query<TransactionCreateParams>,
36
) -> impl IntoResponse {
37
    let template = TransactionCreatePage {
38
        from_account: params.from_account,
39
        tags: Vec::new(),
40
        transaction_id: None,
41
    };
42
    HtmlTemplate(template)
43
}
44

            
45
#[derive(Template)]
46
#[template(path = "components/transaction/create.html")]
47
struct TransactionFormTemplate {
48
    tags: Vec<finance::tag::Tag>,
49
    transaction_id: Option<String>,
50
}
51

            
52
pub async fn transaction_form() -> impl IntoResponse {
53
    let template = TransactionFormTemplate {
54
        tags: Vec::new(),
55
        transaction_id: None,
56
    };
57
    HtmlTemplate(template)
58
}
59

            
60
#[derive(Deserialize, Debug)]
61
pub struct TransactionForm {
62
    splits: Vec<SplitData>,
63
    note: Option<String>,
64
    date: Option<String>,
65
}
66

            
67
16
pub async fn transaction_submit(
68
16
    State(_data): State<Arc<AppState>>,
69
16
    Extension(jwt_auth): Extension<JWTAuthMiddleware>,
70
16
    Json(form): Json<TransactionForm>,
71
24
) -> Result<impl IntoResponse, (StatusCode, Json<serde_json::Value>)> {
72
16
    let user = &jwt_auth.user;
73

            
74
    // Validate splits
75
16
    validate_splits_not_empty(&form.splits)?;
76

            
77
    // Parse date
78
14
    let post_date = parse_transaction_date(form.date.as_deref());
79
14
    let post_date_utc = post_date.and_utc();
80
14
    let enter_date_utc = Local::now().naive_utc().and_utc();
81

            
82
    // Create transaction ID
83
14
    let tx_id = Uuid::new_v4();
84

            
85
    // Process splits using shared utility
86
14
    let mut split_entities = Vec::new();
87
14
    let mut prices = Vec::new();
88
14
    let mut split_tags_to_create = Vec::new();
89

            
90
14
    for split_data in form.splits {
91
14
        let processed = process_split_data(user.id, tx_id, split_data).await?;
92

            
93
        let from_split_id = processed.from_split.id;
94
        let to_split_id = processed.to_split.id;
95

            
96
        split_entities.push(FinanceEntity::Split(processed.from_split));
97
        split_entities.push(FinanceEntity::Split(processed.to_split));
98

            
99
        if let Some(price) = processed.price {
100
            prices.push(FinanceEntity::Price(price));
101
        }
102

            
103
        if let Some(tags) = processed.from_split_tags {
104
            for tag in tags {
105
                split_tags_to_create.push((from_split_id, tag));
106
            }
107
        }
108

            
109
        if let Some(tags) = processed.to_split_tags {
110
            for tag in tags {
111
                split_tags_to_create.push((to_split_id, tag));
112
            }
113
        }
114
    }
115

            
116
    // Execute command
117
    let mut cmd = server::command::transaction::CreateTransaction::new()
118
        .user_id(user.id)
119
        .splits(split_entities)
120
        .id(tx_id)
121
        .post_date(post_date_utc)
122
        .enter_date(enter_date_utc);
123

            
124
    if !prices.is_empty() {
125
        cmd = cmd.prices(prices);
126
    }
127

            
128
    if let Some(note) = form.note.as_deref()
129
        && !note.trim().is_empty()
130
    {
131
        cmd = cmd.note(note.to_string());
132
    }
133

            
134
    match cmd.run().await {
135
        Ok(result) => {
136
            // Create split tags after transaction is successfully created
137
            let server_user = server::user::User { id: user.id };
138
            for (split_id, tag_data) in split_tags_to_create {
139
                server_user
140
                    .create_split_tag(
141
                        split_id,
142
                        tag_data.name,
143
                        tag_data.value,
144
                        tag_data.description,
145
                    )
146
                    .await
147
                    .map_err(|e| {
148
                        let error_response = serde_json::json!({
149
                            "status": "fail",
150
                            "message": format!("Failed to create split tag: {:?}", e),
151
                        });
152
                        log::error!("Failed to create split tag for split {}: {:?}", split_id, e);
153
                        (StatusCode::INTERNAL_SERVER_ERROR, Json(error_response))
154
                    })?;
155
            }
156

            
157
            match result {
158
                Some(CmdResult::Entity(FinanceEntity::Transaction(tx))) => Ok(format!(
159
                    "{}: {}",
160
                    t!("New transaction created with ID"),
161
                    tx.id
162
                )),
163
                _ => Ok(t!("New transaction created successfully").to_string()),
164
            }
165
        }
166
        Err(e) => {
167
            let error_response = serde_json::json!({
168
                "status": "fail",
169
                "message": format!("Failed to create transaction: {:?}", e),
170
            });
171

            
172
            log::error!("Failed to create transaction: {e:?}");
173
            Err((StatusCode::INTERNAL_SERVER_ERROR, Json(error_response)))
174
        }
175
    }
176
16
}