diff --git a/src/model/gains.rs b/src/model/gains.rs index 17ab89a..6dc5724 100644 --- a/src/model/gains.rs +++ b/src/model/gains.rs @@ -613,33 +613,19 @@ impl CapGainsWorksheet { for row in &self.worksheet { for detail in &row.trade_details { - let (is_long, basis_date) = match &detail.net_gain { - GainTerm::LongUs(p) | GainTerm::LongBonaFide(p) => (true, p.basis_date), - GainTerm::Long { us, .. } => (true, us.basis_date), - GainTerm::ShortUs(p) | GainTerm::ShortBonaFide(p) => (false, p.basis_date), - GainTerm::Short { us, .. } => (false, us.basis_date), - }; - - if is_long { - dates.lt_earliest_acquired = Some(match dates.lt_earliest_acquired { - Some(existing) => existing.min(basis_date), - None => basis_date, - }); - dates.lt_latest_sold = Some(match dates.lt_latest_sold { - Some(existing) => existing.max(row.event_date), - None => row.event_date, - }); - } else { - dates.st_earliest_acquired = Some(match dates.st_earliest_acquired { - Some(existing) => existing.min(basis_date), - None => basis_date, - }); - dates.st_latest_sold = Some(match dates.st_latest_sold { - Some(existing) => existing.max(row.event_date), - None => row.event_date, - }); + dates.update(&detail.net_gain, row.event_date); + } + for fee in &row.tx_fees { + dates.update(&fee.net_loss, row.event_date); + } + for detail in &row.position_details { + if detail.proceeds_bona_fide.is_some() { + dates.update_short(row.event_date, row.event_date); } } + for fee in &row.position_fees { + dates.update(&fee.net_loss, row.event_date); + } } dates @@ -967,6 +953,41 @@ pub struct PrStatement24Dates { pub st_latest_sold: Option>, } +impl PrStatement24Dates { + fn update(&mut self, gain_term: &GainTerm, event_date: DateTime) { + let (is_long, basis_date) = match gain_term { + GainTerm::LongUs(p) | GainTerm::LongBonaFide(p) => (true, p.basis_date), + GainTerm::Long { us, .. } => (true, us.basis_date), + GainTerm::ShortUs(p) | GainTerm::ShortBonaFide(p) => (false, p.basis_date), + GainTerm::Short { us, .. } => (false, us.basis_date), + }; + + if is_long { + self.lt_earliest_acquired = Some(match self.lt_earliest_acquired { + Some(existing) => existing.min(basis_date), + None => basis_date, + }); + self.lt_latest_sold = Some(match self.lt_latest_sold { + Some(existing) => existing.max(event_date), + None => event_date, + }); + } else { + self.update_short(basis_date, event_date); + } + } + + fn update_short(&mut self, basis_date: DateTime, event_date: DateTime) { + self.st_earliest_acquired = Some(match self.st_earliest_acquired { + Some(existing) => existing.min(basis_date), + None => basis_date, + }); + self.st_latest_sold = Some(match self.st_latest_sold { + Some(existing) => existing.max(event_date), + None => event_date, + }); + } +} + /// One row of PR Statement-24 (F1 Part III statement 24 :: capital gains). #[derive(Debug)] struct PrStatement24Row { diff --git a/src/model/gains/tests.rs b/src/model/gains/tests.rs index 95a9faa..af53c8e 100644 --- a/src/model/gains/tests.rs +++ b/src/model/gains/tests.rs @@ -1,10 +1,53 @@ use super::*; +use crate::model::kraken_amount::{BitcoinAmount, KrakenAmount}; use chrono::TimeZone; fn usd(n: i64) -> UsdAmount { UsdAmount::from_int(n) } +/// Regression test for issue #3: pr_statement24_dates() must collect dates from +/// tx_fees, not only trade_details. A worksheet with fee-only bona fide activity +/// must still produce dates for the PR Statement-24 row. +#[test] +fn pr_statement24_dates_from_fees_only() { + let basis_date = Utc.with_ymd_and_hms(2024, 6, 1, 0, 0, 0).unwrap(); + let event_date = Utc.with_ymd_and_hms(2024, 12, 1, 0, 0, 0).unwrap(); + + let worksheet = CapGainsWorksheet { + worksheet: vec![CapGainsWorksheetRow { + event_date, + internal_account: String::new(), + ledger_row_id: String::new(), + event_subtype: EventSubType::Trade, + event_name: String::new(), + asset_out_exchange_rate: String::new(), + asset_in_exchange_rate: String::new(), + proceeds: usd(0), + trade_details: vec![], + income_details: vec![], + position_details: vec![], + tx_fees: vec![EventFee { + asset_fee: KrakenAmount::Btc(BitcoinAmount::default()), + net_loss: GainTerm::ShortBonaFide(GainPortion { + basis: usd(5000), + basis_date, + basis_synthetic_id: String::new(), + net_gain: usd(-5000), + }), + }], + position_fees: vec![], + }], + }; + + let dates = worksheet.pr_statement24_dates(); + + assert_eq!(dates.st_earliest_acquired, Some(basis_date)); + assert_eq!(dates.st_latest_sold, Some(event_date)); + assert_eq!(dates.lt_earliest_acquired, None); + assert_eq!(dates.lt_latest_sold, None); +} + #[test] fn pr_statement24_from_sums() { let sums = Sums {