From 7b2503c626bb3fd32c0a1cbcacb685d87d10bced Mon Sep 17 00:00:00 2001 From: zensgit <77236085+zensgit@users.noreply.github.com> Date: Wed, 15 Oct 2025 23:23:14 +0800 Subject: [PATCH 1/2] api: move Transaction model and service to Decimal amounts; refresh SQLx cache verified --- jive-api/src/models/transaction.rs | 7 ++++--- jive-api/src/services/transaction_service.rs | 9 +++++---- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/jive-api/src/models/transaction.rs b/jive-api/src/models/transaction.rs index dd9204a9..f12e9ac9 100644 --- a/jive-api/src/models/transaction.rs +++ b/jive-api/src/models/transaction.rs @@ -1,4 +1,5 @@ use chrono::{DateTime, Utc}; +use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use sqlx::FromRow; use uuid::Uuid; @@ -9,7 +10,7 @@ pub struct Transaction { pub ledger_id: Uuid, pub account_id: Uuid, pub transaction_date: DateTime, - pub amount: f64, + pub amount: Decimal, pub transaction_type: TransactionType, pub category_id: Option, pub category_name: Option, @@ -45,7 +46,7 @@ pub struct TransactionCreate { pub ledger_id: Uuid, pub account_id: Uuid, pub transaction_date: DateTime, - pub amount: f64, + pub amount: Decimal, pub transaction_type: TransactionType, pub category_id: Option, pub category_name: Option, @@ -58,7 +59,7 @@ pub struct TransactionCreate { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TransactionUpdate { pub transaction_date: Option>, - pub amount: Option, + pub amount: Option, pub transaction_type: Option, pub category_id: Option, pub category_name: Option, diff --git a/jive-api/src/services/transaction_service.rs b/jive-api/src/services/transaction_service.rs index 2d0cfc5b..4f939a85 100644 --- a/jive-api/src/services/transaction_service.rs +++ b/jive-api/src/services/transaction_service.rs @@ -1,6 +1,7 @@ use crate::error::{ApiError, ApiResult}; use crate::models::transaction::{Transaction, TransactionCreate, TransactionType}; use chrono::{DateTime, Utc}; +use rust_decimal::Decimal; use sqlx::PgPool; use std::collections::HashMap; use uuid::Uuid; @@ -28,7 +29,7 @@ impl TransactionService { let data_snapshot = data.clone(); // 获取账户当前余额 - let current_balance: Option<(f64,)> = + let current_balance: Option<(Decimal,)> = sqlx::query_as("SELECT current_balance FROM accounts WHERE id = $1 FOR UPDATE") .bind(data.account_id) .fetch_optional(&mut *tx) @@ -127,7 +128,7 @@ impl TransactionService { target_account_id: Uuid, ) -> ApiResult<()> { // 获取目标账户余额 - let target_balance: Option<(f64,)> = + let target_balance: Option<(Decimal,)> = sqlx::query_as("SELECT current_balance FROM accounts WHERE id = $1 FOR UPDATE") .bind(target_account_id) .fetch_optional(&mut **tx) @@ -190,14 +191,14 @@ impl TransactionService { .map_err(|e| ApiError::DatabaseError(e.to_string()))?; let mut created_transactions = Vec::new(); - let mut account_balances: HashMap = HashMap::new(); + let mut account_balances: HashMap = HashMap::new(); // 预加载所有相关账户的余额 for trans in &transactions { if let std::collections::hash_map::Entry::Vacant(e) = account_balances.entry(trans.account_id) { - let balance: Option<(f64,)> = + let balance: Option<(Decimal,)> = sqlx::query_as("SELECT current_balance FROM accounts WHERE id = $1") .bind(trans.account_id) .fetch_optional(&mut *tx) From b67abed2aab2954d456371f8faf9ed4911ab3e17 Mon Sep 17 00:00:00 2001 From: zensgit <77236085+zensgit@users.noreply.github.com> Date: Wed, 15 Oct 2025 23:55:00 +0800 Subject: [PATCH 2/2] tests: add transaction amount serialization contract (Decimal as string) --- .../tests/contract_decimal_transaction.rs | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 jive-api/tests/contract_decimal_transaction.rs diff --git a/jive-api/tests/contract_decimal_transaction.rs b/jive-api/tests/contract_decimal_transaction.rs new file mode 100644 index 00000000..7bb39411 --- /dev/null +++ b/jive-api/tests/contract_decimal_transaction.rs @@ -0,0 +1,31 @@ +use chrono::{TimeZone, Utc}; +use rust_decimal::Decimal; +use serde_json::Value; + +#[test] +fn transaction_decimal_amount_serializes_as_string() { + use jive_money_api::models::transaction::{Transaction, TransactionStatus, TransactionType}; + use uuid::Uuid; + + let tx = Transaction { + id: Uuid::nil(), + ledger_id: Uuid::nil(), + account_id: Uuid::nil(), + transaction_date: Utc.timestamp_opt(1_700_000_000, 0).unwrap(), + amount: Decimal::new(12345, 2), // 123.45 + transaction_type: TransactionType::Income, + category_id: None, + category_name: Some("Salary".to_string()), + payee: Some("Company".to_string()), + notes: None, + status: TransactionStatus::Cleared, + related_transaction_id: None, + created_at: Utc.timestamp_opt(1_700_000_000, 0).unwrap(), + updated_at: Utc.timestamp_opt(1_700_000_000, 0).unwrap(), + }; + + let val: Value = serde_json::to_value(&tx).expect("serialize transaction"); + assert!(val.get("amount").and_then(|v| v.as_str()).is_some(), "amount should be string"); + assert_eq!(val["amount"].as_str().unwrap(), "123.45"); +} +