1
//! Transaction-edit open + submit logic for `App`.
2

            
3
use cli_core::eval::escape_str;
4
use cli_core::forms::{
5
    EditableTransaction, LogicalSplitInput, build_transaction_update_payload,
6
    parse_editable_transaction,
7
};
8

            
9
use crate::app::App;
10
use crate::form::Form;
11
use crate::modal::Modal;
12
use crate::route::RouteCtx;
13
use crate::view::ViewId;
14

            
15
impl App {
16
    /// Dispatch `(get-transaction-detail "<id>")` with `RouteCtx::TransactionEdit`.
17
    ///
18
    /// The async reply opens the edit form (or sets a status on parse failure).
19
2
    pub fn open_transaction_edit_async(&mut self, id: &str) {
20
2
        if self.console_eval.is_none() {
21
2
            self.status = "console not connected".to_string();
22
2
            return;
23
        }
24
        let form = format!("(get-transaction-detail {})", escape_str(id));
25
        if self
26
            .dispatch_eval(ViewId::Transactions, RouteCtx::TransactionEdit, form)
27
            .is_none()
28
        {
29
            self.status = "eval worker stopped".to_string();
30
        }
31
2
    }
32

            
33
    /// Called from `deliver_reply` when a `TransactionEdit` route reply arrives.
34
1
    pub(super) fn deliver_transaction_edit_reply(&mut self, wire: &str) {
35
1
        match parse_editable_transaction(wire) {
36
            Ok(et) => self.open_edit_form_from(&et),
37
1
            Err(msg) => self.status = format!("can't edit this transaction: {msg}"),
38
        }
39
1
    }
40

            
41
    fn open_edit_form_from(&mut self, et: &EditableTransaction) {
42
        let mode = self.edit_mode;
43
        self.overlays
44
            .push(Modal::Form(Form::transaction_edit(mode, et)));
45
        self.fetch_transaction_form_options();
46
    }
47

            
48
    /// Submit the `update-transaction-logical` payload for the edit form.
49
    ///
50
    /// Returns `false` when the eval worker is absent or the payload fails to build.
51
1
    pub fn submit_transaction_edit(
52
1
        &mut self,
53
1
        id: &str,
54
1
        splits: &[LogicalSplitInput],
55
1
        note: &str,
56
1
        date: &str,
57
1
    ) -> bool {
58
1
        let form = match build_transaction_update_payload(id, splits, note, date) {
59
1
            Ok(f) => f,
60
            Err(e) => {
61
                self.status = format!("transaction payload error: {e}");
62
                return false;
63
            }
64
        };
65
1
        if self.console_eval.is_none() {
66
1
            self.status = "console not connected".to_string();
67
1
            return false;
68
        }
69
        match self.dispatch_eval(
70
            ViewId::Transactions,
71
            RouteCtx::Mutation {
72
                refresh: ViewId::Transactions,
73
            },
74
            form,
75
        ) {
76
            Some(_) => true,
77
            None => {
78
                self.status = "eval worker stopped".to_string();
79
                false
80
            }
81
        }
82
1
    }
83
}
84

            
85
#[cfg(test)]
86
mod tests {
87
    use super::*;
88
    use crate::widgets::EditMode;
89
    use sqlx::types::Uuid;
90

            
91
2
    fn make_app() -> App {
92
2
        App::new(Uuid::new_v4(), EditMode::Emacs)
93
2
    }
94

            
95
    #[test]
96
1
    fn deliver_transaction_edit_reply_err_path_sets_status() {
97
1
        let mut app = make_app();
98
        // Garbage wire → parse_editable_transaction returns Err → descriptive status.
99
1
        app.deliver_transaction_edit_reply("garbage wire");
100
1
        assert!(
101
1
            app.status.starts_with("can't edit this transaction:"),
102
            "error reply sets descriptive status: {}",
103
            app.status
104
        );
105
1
        assert!(app.overlays.is_empty(), "no form opened on parse error");
106
1
    }
107

            
108
    #[test]
109
1
    fn submit_transaction_edit_without_eval_sets_status() {
110
        use cli_core::forms::LogicalSplitInput;
111
1
        let splits = vec![LogicalSplitInput {
112
1
            from: "from-uuid".to_string(),
113
1
            to: "to-uuid".to_string(),
114
1
            from_commodity: "comm-uuid".to_string(),
115
1
            to_commodity: "comm-uuid".to_string(),
116
1
            amount: "10".to_string(),
117
1
            to_amount: None,
118
1
        }];
119
1
        let mut app = make_app();
120
1
        let ok = app.submit_transaction_edit("tx-id", &splits, "note", "2024-01-01");
121
1
        assert!(!ok, "no eval worker → returns false");
122
1
        assert_eq!(app.status, "console not connected");
123
1
    }
124
}