Skip to main content

tui/app/
edit.rs

1//! Transaction-edit open + submit logic for `App`.
2
3use cli_core::eval::escape_str;
4use cli_core::forms::{
5    EditableTransaction, LogicalSplitInput, build_transaction_update_payload,
6    parse_editable_transaction,
7};
8
9use crate::app::App;
10use crate::form::Form;
11use crate::modal::Modal;
12use crate::route::RouteCtx;
13use crate::view::ViewId;
14
15impl 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    pub fn open_transaction_edit_async(&mut self, id: &str) {
20        if self.console_eval.is_none() {
21            self.status = "console not connected".to_string();
22            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    }
32
33    /// Called from `deliver_reply` when a `TransactionEdit` route reply arrives.
34    pub(super) fn deliver_transaction_edit_reply(&mut self, wire: &str) {
35        match parse_editable_transaction(wire) {
36            Ok(et) => self.open_edit_form_from(&et),
37            Err(msg) => self.status = format!("can't edit this transaction: {msg}"),
38        }
39    }
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    pub fn submit_transaction_edit(
52        &mut self,
53        id: &str,
54        splits: &[LogicalSplitInput],
55        note: &str,
56        date: &str,
57    ) -> bool {
58        let form = match build_transaction_update_payload(id, splits, note, date) {
59            Ok(f) => f,
60            Err(e) => {
61                self.status = format!("transaction payload error: {e}");
62                return false;
63            }
64        };
65        if self.console_eval.is_none() {
66            self.status = "console not connected".to_string();
67            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    }
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88    use crate::widgets::EditMode;
89    use sqlx::types::Uuid;
90
91    fn make_app() -> App {
92        App::new(Uuid::new_v4(), EditMode::Emacs)
93    }
94
95    #[test]
96    fn deliver_transaction_edit_reply_err_path_sets_status() {
97        let mut app = make_app();
98        // Garbage wire → parse_editable_transaction returns Err → descriptive status.
99        app.deliver_transaction_edit_reply("garbage wire");
100        assert!(
101            app.status.starts_with("can't edit this transaction:"),
102            "error reply sets descriptive status: {}",
103            app.status
104        );
105        assert!(app.overlays.is_empty(), "no form opened on parse error");
106    }
107
108    #[test]
109    fn submit_transaction_edit_without_eval_sets_status() {
110        use cli_core::forms::LogicalSplitInput;
111        let splits = vec![LogicalSplitInput {
112            from: "from-uuid".to_string(),
113            to: "to-uuid".to_string(),
114            from_commodity: "comm-uuid".to_string(),
115            to_commodity: "comm-uuid".to_string(),
116            amount: "10".to_string(),
117            to_amount: None,
118        }];
119        let mut app = make_app();
120        let ok = app.submit_transaction_edit("tx-id", &splits, "note", "2024-01-01");
121        assert!(!ok, "no eval worker → returns false");
122        assert_eq!(app.status, "console not connected");
123    }
124}