Formatting fixes
The result of `cargo fmt`.
This commit is contained in:
@@ -1,15 +1,17 @@
|
||||
use async_trait::async_trait;
|
||||
use anyhow::Result;
|
||||
use tracing::instrument;
|
||||
use crate::core::ports::{TransactionDestination, TransactionMatch};
|
||||
use crate::core::models::BankTransaction;
|
||||
use crate::core::ports::{TransactionDestination, TransactionMatch};
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
use chrono::NaiveDate;
|
||||
use firefly_client::client::FireflyClient;
|
||||
use firefly_client::models::{TransactionStore, TransactionSplitStore, TransactionUpdate, TransactionSplitUpdate};
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use firefly_client::models::{
|
||||
TransactionSplitStore, TransactionSplitUpdate, TransactionStore, TransactionUpdate,
|
||||
};
|
||||
use rust_decimal::Decimal;
|
||||
use std::str::FromStr;
|
||||
use chrono::NaiveDate;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::instrument;
|
||||
|
||||
pub struct FireflyAdapter {
|
||||
client: Arc<Mutex<FireflyClient>>,
|
||||
@@ -29,7 +31,7 @@ impl TransactionDestination for FireflyAdapter {
|
||||
async fn resolve_account_id(&self, iban: &str) -> Result<Option<String>> {
|
||||
let client = self.client.lock().await;
|
||||
let accounts = client.search_accounts(iban).await?;
|
||||
|
||||
|
||||
// Look for exact match on IBAN, ensuring account is active
|
||||
for acc in accounts.data {
|
||||
// Filter for active accounts only (default is usually active, but let's check if attribute exists)
|
||||
@@ -42,11 +44,11 @@ impl TransactionDestination for FireflyAdapter {
|
||||
|
||||
if let Some(acc_iban) = acc.attributes.iban {
|
||||
if acc_iban.replace(" ", "") == iban.replace(" ", "") {
|
||||
return Ok(Some(acc.id));
|
||||
return Ok(Some(acc.id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
@@ -57,19 +59,19 @@ impl TransactionDestination for FireflyAdapter {
|
||||
// For typical users, 50 is enough. If needed we can loop pages.
|
||||
// The client `get_accounts` method hardcodes limit=default. We should probably expose a list_all method or loop here.
|
||||
// For now, let's assume page 1 covers it or use search.
|
||||
|
||||
|
||||
let accounts = client.get_accounts("").await?; // Argument ignored in current impl
|
||||
let mut ibans = Vec::new();
|
||||
|
||||
|
||||
for acc in accounts.data {
|
||||
let is_active = acc.attributes.active.unwrap_or(true);
|
||||
if is_active {
|
||||
if let Some(iban) = acc.attributes.iban {
|
||||
if !iban.is_empty() {
|
||||
ibans.push(iban);
|
||||
}
|
||||
}
|
||||
}
|
||||
let is_active = acc.attributes.active.unwrap_or(true);
|
||||
if is_active {
|
||||
if let Some(iban) = acc.attributes.iban {
|
||||
if !iban.is_empty() {
|
||||
ibans.push(iban);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(ibans)
|
||||
}
|
||||
@@ -78,63 +80,71 @@ impl TransactionDestination for FireflyAdapter {
|
||||
async fn get_last_transaction_date(&self, account_id: &str) -> Result<Option<NaiveDate>> {
|
||||
let client = self.client.lock().await;
|
||||
// Fetch latest 1 transaction
|
||||
let tx_list = client.list_account_transactions(account_id, None, None).await?;
|
||||
|
||||
let tx_list = client
|
||||
.list_account_transactions(account_id, None, None)
|
||||
.await?;
|
||||
|
||||
if let Some(first) = tx_list.data.first() {
|
||||
if let Some(split) = first.attributes.transactions.first() {
|
||||
// Format is usually YYYY-MM-DDT... or YYYY-MM-DD
|
||||
let date_str = split.date.split('T').next().unwrap_or(&split.date);
|
||||
if let Ok(date) = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") {
|
||||
return Ok(Some(date));
|
||||
}
|
||||
// Format is usually YYYY-MM-DDT... or YYYY-MM-DD
|
||||
let date_str = split.date.split('T').next().unwrap_or(&split.date);
|
||||
if let Ok(date) = NaiveDate::parse_from_str(date_str, "%Y-%m-%d") {
|
||||
return Ok(Some(date));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
#[instrument(skip(self))]
|
||||
async fn find_transaction(&self, account_id: &str, tx: &BankTransaction) -> Result<Option<TransactionMatch>> {
|
||||
async fn find_transaction(
|
||||
&self,
|
||||
account_id: &str,
|
||||
tx: &BankTransaction,
|
||||
) -> Result<Option<TransactionMatch>> {
|
||||
let client = self.client.lock().await;
|
||||
|
||||
|
||||
// Search window: +/- 3 days
|
||||
let start_date = tx.date - chrono::Duration::days(3);
|
||||
let end_date = tx.date + chrono::Duration::days(3);
|
||||
|
||||
let tx_list = client.list_account_transactions(
|
||||
account_id,
|
||||
Some(&start_date.format("%Y-%m-%d").to_string()),
|
||||
Some(&end_date.format("%Y-%m-%d").to_string())
|
||||
).await?;
|
||||
|
||||
let tx_list = client
|
||||
.list_account_transactions(
|
||||
account_id,
|
||||
Some(&start_date.format("%Y-%m-%d").to_string()),
|
||||
Some(&end_date.format("%Y-%m-%d").to_string()),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// Filter logic
|
||||
for existing_tx in tx_list.data {
|
||||
for split in existing_tx.attributes.transactions {
|
||||
// 1. Check Amount (exact match absolute value)
|
||||
if let Ok(amount) = Decimal::from_str(&split.amount) {
|
||||
if amount.abs() == tx.amount.abs() {
|
||||
// 2. Check External ID
|
||||
if let Some(ref ext_id) = split.external_id {
|
||||
if ext_id == &tx.internal_id {
|
||||
return Ok(Some(TransactionMatch {
|
||||
id: existing_tx.id.clone(),
|
||||
has_external_id: true,
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
// 3. "Naked" transaction match (Heuristic)
|
||||
// If currency matches
|
||||
if let Some(ref code) = split.currency_code {
|
||||
if code != &tx.currency {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(TransactionMatch {
|
||||
id: existing_tx.id.clone(),
|
||||
has_external_id: false,
|
||||
}));
|
||||
}
|
||||
}
|
||||
if amount.abs() == tx.amount.abs() {
|
||||
// 2. Check External ID
|
||||
if let Some(ref ext_id) = split.external_id {
|
||||
if ext_id == &tx.internal_id {
|
||||
return Ok(Some(TransactionMatch {
|
||||
id: existing_tx.id.clone(),
|
||||
has_external_id: true,
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
// 3. "Naked" transaction match (Heuristic)
|
||||
// If currency matches
|
||||
if let Some(ref code) = split.currency_code {
|
||||
if code != &tx.currency {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(Some(TransactionMatch {
|
||||
id: existing_tx.id.clone(),
|
||||
has_external_id: false,
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,22 +159,42 @@ impl TransactionDestination for FireflyAdapter {
|
||||
// Map to Firefly Transaction
|
||||
let is_credit = tx.amount.is_sign_positive();
|
||||
let transaction_type = if is_credit { "deposit" } else { "withdrawal" };
|
||||
|
||||
|
||||
let split = TransactionSplitStore {
|
||||
transaction_type: transaction_type.to_string(),
|
||||
date: tx.date.format("%Y-%m-%d").to_string(),
|
||||
amount: tx.amount.abs().to_string(),
|
||||
description: tx.description.clone(),
|
||||
source_id: if !is_credit { Some(account_id.to_string()) } else { None },
|
||||
source_name: if is_credit { tx.counterparty_name.clone().or(Some("Unknown Sender".to_string())) } else { None },
|
||||
destination_id: if is_credit { Some(account_id.to_string()) } else { None },
|
||||
destination_name: if !is_credit { tx.counterparty_name.clone().or(Some("Unknown Recipient".to_string())) } else { None },
|
||||
source_id: if !is_credit {
|
||||
Some(account_id.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
source_name: if is_credit {
|
||||
tx.counterparty_name
|
||||
.clone()
|
||||
.or(Some("Unknown Sender".to_string()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
destination_id: if is_credit {
|
||||
Some(account_id.to_string())
|
||||
} else {
|
||||
None
|
||||
},
|
||||
destination_name: if !is_credit {
|
||||
tx.counterparty_name
|
||||
.clone()
|
||||
.or(Some("Unknown Recipient".to_string()))
|
||||
} else {
|
||||
None
|
||||
},
|
||||
currency_code: Some(tx.currency.clone()),
|
||||
foreign_amount: tx.foreign_amount.map(|d| d.abs().to_string()),
|
||||
foreign_currency_code: tx.foreign_currency.clone(),
|
||||
external_id: Some(tx.internal_id.clone()),
|
||||
};
|
||||
|
||||
|
||||
let store = TransactionStore {
|
||||
transactions: vec![split],
|
||||
apply_rules: Some(true),
|
||||
@@ -183,6 +213,9 @@ impl TransactionDestination for FireflyAdapter {
|
||||
external_id: Some(external_id.to_string()),
|
||||
}],
|
||||
};
|
||||
client.update_transaction(id, update).await.map_err(|e| e.into())
|
||||
client
|
||||
.update_transaction(id, update)
|
||||
.await
|
||||
.map_err(|e| e.into())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user