|
@@ -144,19 +144,20 @@ fn check_balance_group<'a>(
|
|
let first = group[0].borrow();
|
|
let first = group[0].borrow();
|
|
let report = ariadne::Report::build(
|
|
let report = ariadne::Report::build(
|
|
ariadne::ReportKind::Error,
|
|
ariadne::ReportKind::Error,
|
|
- first.source.unwrap().union(first.change_for(account).unwrap().source.unwrap()),
|
|
|
|
- )
|
|
|
|
- .with_message("No valid ordering of transactions to satisfy balance assertions")
|
|
|
|
- .with_labels(
|
|
|
|
- group
|
|
|
|
- .iter()
|
|
|
|
- .map(|txn| {
|
|
|
|
- let txn = txn.borrow();
|
|
|
|
- let span = txn.source.unwrap().union(txn.change_for(account).unwrap().source.unwrap());
|
|
|
|
- ariadne::Label::new(span)
|
|
|
|
- .with_message("transaction in group")
|
|
|
|
- }),
|
|
|
|
|
|
+ first
|
|
|
|
+ .source
|
|
|
|
+ .unwrap()
|
|
|
|
+ .union(first.change_for(account).unwrap().source.unwrap()),
|
|
)
|
|
)
|
|
|
|
+ .with_message(format!("No valid ordering of transactions to satisfy balance assertions in {account}"))
|
|
|
|
+ .with_labels(group.iter().map(|txn| {
|
|
|
|
+ let txn = txn.borrow();
|
|
|
|
+ let span = txn
|
|
|
|
+ .source
|
|
|
|
+ .unwrap()
|
|
|
|
+ .union(txn.change_for(account).unwrap().source.unwrap());
|
|
|
|
+ ariadne::Label::new(span).with_message("transaction in group")
|
|
|
|
+ }))
|
|
.finish();
|
|
.finish();
|
|
|
|
|
|
Err(DataError::Report(Box::new(report)))
|
|
Err(DataError::Report(Box::new(report)))
|
|
@@ -171,7 +172,7 @@ fn check_balances(root: &data::Hoard) -> Result<(), DataError> {
|
|
};
|
|
};
|
|
|
|
|
|
let mut running_balance = BTreeMap::<data::UnitName, Spanned<data::Decimal>>::new();
|
|
let mut running_balance = BTreeMap::<data::UnitName, Spanned<data::Decimal>>::new();
|
|
- let date_groups = ledger.iter().chunk_by(|tx| tx.borrow().datestamp);
|
|
|
|
|
|
+ let date_groups = ledger.iter().chunk_by(|tx| tx.borrow().datestamp_for(account));
|
|
|
|
|
|
for group in date_groups.into_iter() {
|
|
for group in date_groups.into_iter() {
|
|
check_balance_group(account, &mut running_balance, group.1)?;
|
|
check_balance_group(account, &mut running_balance, group.1)?;
|
|
@@ -183,6 +184,15 @@ fn check_balances(root: &data::Hoard) -> Result<(), DataError> {
|
|
fn check_merge_groups(root: &data::Hoard) -> Result<(), DataError> {
|
|
fn check_merge_groups(root: &data::Hoard) -> Result<(), DataError> {
|
|
log::trace!("Checking merge groups...");
|
|
log::trace!("Checking merge groups...");
|
|
|
|
|
|
|
|
+ let change_signature = |txn: &data::Transaction, c: &data::Change| {
|
|
|
|
+ let mut span = c.account.span().union(c.amount.span()).union(c.unit.span());
|
|
|
|
+ if let Some(ds) = c.datestamp {
|
|
|
|
+ span = span.union(ds.span());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ (Spanned::new((), span), c.account.0, c.amount.0, c.datestamp.map(|d| d.0).unwrap_or(txn.datestamp))
|
|
|
|
+ };
|
|
|
|
+
|
|
for account in root.account_names() {
|
|
for account in root.account_names() {
|
|
let Some(ledger) = root.ledger_data_for(account) else {
|
|
let Some(ledger) = root.ledger_data_for(account) else {
|
|
continue;
|
|
continue;
|
|
@@ -190,48 +200,79 @@ fn check_merge_groups(root: &data::Hoard) -> Result<(), DataError> {
|
|
|
|
|
|
for txn_ref in ledger {
|
|
for txn_ref in ledger {
|
|
let txn = txn_ref.borrow();
|
|
let txn = txn_ref.borrow();
|
|
- let Some(group_id) = txn.merge_group() else { continue };
|
|
|
|
|
|
+ let Some(group_id) = txn.merge_group() else {
|
|
|
|
+ continue;
|
|
|
|
+ };
|
|
let Some(group) = root.merge_group(group_id) else {
|
|
let Some(group) = root.merge_group(group_id) else {
|
|
todo!()
|
|
todo!()
|
|
};
|
|
};
|
|
|
|
|
|
- let txn_changes = txn.changes.iter().map(|c| (c.account, c.amount, c.unit)).sorted().collect_vec();
|
|
|
|
|
|
+ let txn_changes = txn
|
|
|
|
+ .changes
|
|
|
|
+ .iter()
|
|
|
|
+ .map(|c| change_signature(&txn, c))
|
|
|
|
+ .sorted()
|
|
|
|
+ .collect_vec();
|
|
|
|
|
|
for placeholder_ref in group {
|
|
for placeholder_ref in group {
|
|
let placeholder = placeholder_ref.borrow();
|
|
let placeholder = placeholder_ref.borrow();
|
|
|
|
|
|
- if txn.datestamp != placeholder.datestamp {
|
|
|
|
- let report = ariadne::Report::build(
|
|
|
|
- ariadne::ReportKind::Error,
|
|
|
|
- txn.source.unwrap()
|
|
|
|
- )
|
|
|
|
- .with_label(ariadne::Label::new(txn.source.unwrap()).with_message("this transaction"))
|
|
|
|
- .with_label(ariadne::Label::new(placeholder.source.unwrap()).with_message("and this transaction"))
|
|
|
|
- .with_message("different datestamps despite being in one merge group")
|
|
|
|
- .finish();
|
|
|
|
-
|
|
|
|
- return Err(report.into())
|
|
|
|
|
|
+ if txn.datestamp_for(account) != placeholder.datestamp_for(account) {
|
|
|
|
+ let report =
|
|
|
|
+ ariadne::Report::build(ariadne::ReportKind::Error, txn.source.unwrap())
|
|
|
|
+ .with_label(
|
|
|
|
+ ariadne::Label::new(txn.source.unwrap())
|
|
|
|
+ .with_message("this transaction"),
|
|
|
|
+ )
|
|
|
|
+ .with_label(
|
|
|
|
+ ariadne::Label::new(placeholder.source.unwrap())
|
|
|
|
+ .with_message("and this transaction"),
|
|
|
|
+ )
|
|
|
|
+ .with_message("different datestamps despite being in one merge group")
|
|
|
|
+ .finish();
|
|
|
|
+
|
|
|
|
+ return Err(report.into());
|
|
}
|
|
}
|
|
|
|
|
|
- let placeholder_changes = placeholder.changes.iter().map(|c| (c.account, c.amount, c.unit)).sorted().collect_vec();
|
|
|
|
-
|
|
|
|
- let mut different_changes = txn_changes.iter().zip(placeholder_changes.iter()).filter(|(t,p)| t != p);
|
|
|
|
-
|
|
|
|
- if let Some((t,p)) = different_changes.next() {
|
|
|
|
- let tspan = t.0.span().union(t.1.span()).union(t.2.span());
|
|
|
|
- let pspan = p.0.span().union(p.1.span()).union(p.2.span());
|
|
|
|
-
|
|
|
|
- let report = ariadne::Report::build(
|
|
|
|
- ariadne::ReportKind::Error,
|
|
|
|
- txn.source.unwrap()
|
|
|
|
- )
|
|
|
|
- .with_label(ariadne::Label::new(txn.source.unwrap()).with_message("in this transaction"))
|
|
|
|
- .with_label(ariadne::Label::new(tspan).with_message("this change differs from"))
|
|
|
|
- .with_label(ariadne::Label::new(placeholder.source.unwrap()).with_message("in this transaction"))
|
|
|
|
- .with_label(ariadne::Label::new(pspan).with_message("this change"))
|
|
|
|
- .with_message("merged transactions differ in at least one change")
|
|
|
|
- .finish();
|
|
|
|
- return Err(report.into())
|
|
|
|
|
|
+ let placeholder_changes = placeholder
|
|
|
|
+ .changes
|
|
|
|
+ .iter()
|
|
|
|
+ .map(|c| change_signature(&placeholder, c))
|
|
|
|
+ .sorted()
|
|
|
|
+ .collect_vec();
|
|
|
|
+
|
|
|
|
+ let mut different_changes = txn_changes
|
|
|
|
+ .iter()
|
|
|
|
+ .zip(placeholder_changes.iter())
|
|
|
|
+ .filter(|(t, p)| t != p);
|
|
|
|
+
|
|
|
|
+ if let Some((t, p)) = different_changes.next() {
|
|
|
|
+ let tspan = t.0.span();
|
|
|
|
+ let pspan = p.0.span();
|
|
|
|
+
|
|
|
|
+ println!("t: {t:?}");
|
|
|
|
+ println!("p: {p:?}");
|
|
|
|
+
|
|
|
|
+ println!("txn: {txn:#?}");
|
|
|
|
+ println!("placeholder: {placeholder:#?}");
|
|
|
|
+
|
|
|
|
+ let report =
|
|
|
|
+ ariadne::Report::build(ariadne::ReportKind::Error, txn.source.unwrap())
|
|
|
|
+ .with_label(
|
|
|
|
+ ariadne::Label::new(txn.source.unwrap())
|
|
|
|
+ .with_message("in this transaction"),
|
|
|
|
+ )
|
|
|
|
+ .with_label(
|
|
|
|
+ ariadne::Label::new(tspan).with_message("this change differs from"),
|
|
|
|
+ )
|
|
|
|
+ .with_label(
|
|
|
|
+ ariadne::Label::new(placeholder.source.unwrap())
|
|
|
|
+ .with_message("in this transaction"),
|
|
|
|
+ )
|
|
|
|
+ .with_label(ariadne::Label::new(pspan).with_message("this change"))
|
|
|
|
+ .with_message("merged transactions differ in at least one change")
|
|
|
|
+ .finish();
|
|
|
|
+ return Err(report.into());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|