Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 46 additions & 25 deletions src/model/gains.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -967,6 +953,41 @@ pub struct PrStatement24Dates {
pub st_latest_sold: Option<DateTime<Utc>>,
}

impl PrStatement24Dates {
fn update(&mut self, gain_term: &GainTerm, event_date: DateTime<Utc>) {
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<Utc>, event_date: DateTime<Utc>) {
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 {
Expand Down
43 changes: 43 additions & 0 deletions src/model/gains/tests.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
Loading