Shared parties with exact shares, member capital statements, TINs for any party, K-1 worksheet
Entries may name several parties, each with an exact amount, a percent, or a share derived from the members' capital lines or [[company.members]] shares (entry.parties_set); shares always sum exactly. The 1099 worksheet counts a vendor's share. A member's party page is their partner capital statement, from the same ledger_core::k1 source as the CPA package. books.set_tin stores any party's TIN (members' SSNs for K-1s) in the vault (party.tin_set, last four in the books). The CPA package carries a K-1 member worksheet (k1-members.csv, with a members' total that ties to Schedule L) and the 1099 worksheet with full TINs, saved outside the books only. files_jointly_with notes spouses' two K-1s land on one 1040. Adversarial review fixes: reversed contributions no longer inflate K-1 contributions and distributions; a K-1 row never takes another party's TIN; TIN detection covers dot/slash/dash separators; refusals redact TINs; vendor tagging can't silently replace shared parties; reversals follow re-apportioning. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GWVo7x3UHw5kwCUaWxiejX
This commit is contained in:
1 parent
ef00ccc242
commit
e1b2509c22
32 files changed
+7232
-375
No files matched your search
@@ -0,0 +1,293 @@
|
||||
//! A member's capital account statement, on their party page: the partner
|
||||
//! capital statement (opening balance, contributions of cash and of
|
||||
//! property, share of net income, distributions, closing balance), their
|
||||
//! share, and their part of what the company owes the members jointly.
|
||||
//! Every number is [`crate::books::CapitalStatement`]'s (which reads
|
||||
//! ledger_core::k1, the CPA package's K-1 worksheet); this module only
|
||||
//! lays it out.
|
||||
|
||||
use crate::books::{Books, CapitalKind, CapitalStatement};
|
||||
use crate::fmt;
|
||||
use crate::nav::Route;
|
||||
use crate::theme::{self, pal};
|
||||
use crate::widgets;
|
||||
use egui::{Align, Layout, RichText, Sense, Ui, vec2};
|
||||
use rust_decimal::Decimal;
|
||||
|
||||
/// What a capital line is, in words.
|
||||
pub fn kind_label(k: CapitalKind) -> &'static str {
|
||||
match k {
|
||||
CapitalKind::CashContribution => "Contribution — cash",
|
||||
CapitalKind::PropertyContribution => "Contribution — property",
|
||||
CapitalKind::IncomeShare => "Share of net income",
|
||||
CapitalKind::Distribution => "Distribution",
|
||||
}
|
||||
}
|
||||
|
||||
/// A share as a percent: 0.5 → "50%", 0.3333 → "33.33%".
|
||||
pub fn percent(share: Decimal) -> String {
|
||||
format!("{}%", (share * Decimal::ONE_HUNDRED).normalize())
|
||||
}
|
||||
|
||||
/// One statement row: an indented label, an amount column, and an
|
||||
/// optional total column; `strong` for the balances.
|
||||
fn line(
|
||||
ui: &mut Ui,
|
||||
label: &str,
|
||||
indent: f32,
|
||||
amount: Option<String>,
|
||||
total: Option<String>,
|
||||
strong: bool,
|
||||
) {
|
||||
let p = pal(ui.ctx());
|
||||
let col = 130.0;
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_space(indent);
|
||||
let font = if strong {
|
||||
theme::semibold(14.0)
|
||||
} else {
|
||||
theme::regular(14.0)
|
||||
};
|
||||
ui.label(RichText::new(label).font(font));
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
let t = total.unwrap_or_default();
|
||||
widgets::amount_cell(ui, &t, col, p.text, strong, false);
|
||||
let a = amount.unwrap_or_default();
|
||||
widgets::amount_cell(ui, &a, col, p.text, false, false);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/// A short rule over the amount column (`double`: two, under a total).
|
||||
fn underline(ui: &mut Ui, total_col: bool, double: bool) {
|
||||
let p = pal(ui.ctx());
|
||||
let col = 130.0;
|
||||
let w = ui.available_width();
|
||||
let (rect, _) = ui.allocate_exact_size(vec2(w, if double { 5.0 } else { 3.0 }), Sense::hover());
|
||||
let right = if total_col {
|
||||
rect.right()
|
||||
} else {
|
||||
rect.right() - col - ui.spacing().item_spacing.x
|
||||
};
|
||||
let left = right - col + 30.0;
|
||||
let stroke = egui::Stroke::new(1.0, p.text);
|
||||
let y = rect.top() + 1.0;
|
||||
ui.painter()
|
||||
.line_segment([egui::pos2(left, y), egui::pos2(right, y)], stroke);
|
||||
if double {
|
||||
ui.painter().line_segment(
|
||||
[egui::pos2(left, y + 3.0), egui::pos2(right, y + 3.0)],
|
||||
stroke,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// The member's capital account card.
|
||||
pub fn capital_card(
|
||||
ui: &mut Ui,
|
||||
b: &Books,
|
||||
st: &CapitalStatement,
|
||||
name: &str,
|
||||
go: &mut Option<Route>,
|
||||
) {
|
||||
let p = pal(ui.ctx());
|
||||
let account = b.account_name(st.capital);
|
||||
let (open_account, ()) = widgets::titled_card(
|
||||
ui,
|
||||
"Capital account",
|
||||
|ui| {
|
||||
widgets::link(
|
||||
ui,
|
||||
RichText::new(&account)
|
||||
.font(theme::regular(12.0))
|
||||
.color(p.accent),
|
||||
)
|
||||
.on_hover_text("Open the capital account's register")
|
||||
.clicked()
|
||||
},
|
||||
|ui| {
|
||||
ui.vertical_centered(|ui| {
|
||||
ui.label(RichText::new(name).font(theme::semibold(15.0)));
|
||||
ui.label(
|
||||
RichText::new(format!(
|
||||
"Statement of partner's capital · {} – {}",
|
||||
fmt::date(st.span.0),
|
||||
fmt::date(st.span.1)
|
||||
))
|
||||
.font(theme::regular(12.5))
|
||||
.color(p.muted),
|
||||
);
|
||||
if let Some(j) = &st.files_jointly_with {
|
||||
ui.label(
|
||||
RichText::new(format!(
|
||||
"Files a joint return with {j}: a K-1 each, both on one Form 1040"
|
||||
))
|
||||
.font(theme::regular(12.0))
|
||||
.color(p.muted),
|
||||
);
|
||||
}
|
||||
});
|
||||
ui.add_space(10.0);
|
||||
let amt = |d: Decimal| Some(fmt::accounting(d));
|
||||
line(
|
||||
ui,
|
||||
&format!("Capital, {}", fmt::date(st.span.0)),
|
||||
0.0,
|
||||
None,
|
||||
Some(format!("${}", fmt::accounting(st.opening))),
|
||||
true,
|
||||
);
|
||||
ui.add_space(4.0);
|
||||
ui.label(
|
||||
RichText::new("Add")
|
||||
.font(theme::regular(12.0))
|
||||
.color(p.muted),
|
||||
);
|
||||
line(
|
||||
ui,
|
||||
"Contributions of cash",
|
||||
16.0,
|
||||
amt(st.contributions_cash),
|
||||
None,
|
||||
false,
|
||||
);
|
||||
line(
|
||||
ui,
|
||||
"Contributions of property (at fair market value)",
|
||||
16.0,
|
||||
amt(st.contributions_property),
|
||||
None,
|
||||
false,
|
||||
);
|
||||
line(
|
||||
ui,
|
||||
&format!("Share of net income ({})", percent(st.share)),
|
||||
16.0,
|
||||
amt(st.income_share),
|
||||
None,
|
||||
false,
|
||||
);
|
||||
ui.label(
|
||||
RichText::new("Less")
|
||||
.font(theme::regular(12.0))
|
||||
.color(p.muted),
|
||||
);
|
||||
line(
|
||||
ui,
|
||||
"Distributions",
|
||||
16.0,
|
||||
Some(fmt::accounting(-st.distributions_taken)),
|
||||
None,
|
||||
false,
|
||||
);
|
||||
if !st.other.is_zero() {
|
||||
line(ui, "Other changes", 16.0, amt(st.other), None, false);
|
||||
}
|
||||
underline(ui, false, false);
|
||||
let change = st.contributions_cash + st.contributions_property + st.income_share
|
||||
- st.distributions_taken
|
||||
+ st.other;
|
||||
line(
|
||||
ui,
|
||||
"Net change in capital",
|
||||
0.0,
|
||||
None,
|
||||
Some(fmt::accounting(change)),
|
||||
false,
|
||||
);
|
||||
underline(ui, true, false);
|
||||
line(
|
||||
ui,
|
||||
&format!("Capital, {}", fmt::date(st.span.1)),
|
||||
0.0,
|
||||
None,
|
||||
Some(format!("${}", fmt::accounting(st.closing))),
|
||||
true,
|
||||
);
|
||||
underline(ui, true, true);
|
||||
if !st.income_share.is_zero()
|
||||
&& !st.lines.iter().any(|l| l.kind == CapitalKind::IncomeShare)
|
||||
{
|
||||
ui.label(
|
||||
RichText::new(
|
||||
"No period closed in this span: the share of net income is earned but not yet closed into the capital account.",
|
||||
)
|
||||
.font(theme::regular(11.5))
|
||||
.color(p.faint),
|
||||
);
|
||||
}
|
||||
ui.add_space(10.0);
|
||||
widgets::rule(ui);
|
||||
ui.add_space(6.0);
|
||||
line(
|
||||
ui,
|
||||
"Share of profit, loss, and capital",
|
||||
0.0,
|
||||
None,
|
||||
Some(percent(st.share)),
|
||||
false,
|
||||
);
|
||||
let owed = format!(
|
||||
"Owed to them on Due to Shareholders ({} of {})",
|
||||
percent(st.share),
|
||||
fmt::dollars(st.due_to_members)
|
||||
);
|
||||
line(
|
||||
ui,
|
||||
&owed,
|
||||
0.0,
|
||||
None,
|
||||
Some(fmt::dollars(st.owed_to_member)),
|
||||
false,
|
||||
);
|
||||
if !st.lines.is_empty() {
|
||||
ui.add_space(10.0);
|
||||
ui.collapsing(
|
||||
RichText::new(format!(
|
||||
"{} in this span",
|
||||
fmt::count(st.lines.len(), "capital entry", "capital entries")
|
||||
))
|
||||
.font(theme::regular(12.5)),
|
||||
|ui| {
|
||||
for l in &st.lines {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(
|
||||
RichText::new(fmt::date(l.date))
|
||||
.font(theme::regular(12.5))
|
||||
.color(p.muted),
|
||||
);
|
||||
if widgets::link(
|
||||
ui,
|
||||
RichText::new(format!("#{} {}", l.entry, l.description))
|
||||
.font(theme::regular(12.5)),
|
||||
)
|
||||
.clicked()
|
||||
{
|
||||
*go = Some(Route::Entry(l.entry));
|
||||
}
|
||||
ui.label(
|
||||
RichText::new(kind_label(l.kind))
|
||||
.font(theme::regular(12.0))
|
||||
.color(p.faint),
|
||||
);
|
||||
ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
|
||||
widgets::amount_cell(
|
||||
ui,
|
||||
&fmt::accounting(l.amount),
|
||||
130.0,
|
||||
p.text,
|
||||
false,
|
||||
false,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
if open_account {
|
||||
*go = Some(Route::Account(st.capital));
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ pub fn detail(ui: &mut Ui, b: &Books, e: &EntryInfo, shell: &mut Shell<'_>) {
|
||||
};
|
||||
widgets::pill(ui, e.origin, p.muted, p.hover);
|
||||
if let Some(v) = &e.vendor
|
||||
&& e.party_id.is_none()
|
||||
&& e.parties.is_empty()
|
||||
{
|
||||
widgets::pill(ui, &format!("{} {v}", icon::STOREFRONT), p.muted, p.hover);
|
||||
}
|
||||
@@ -87,27 +87,42 @@ pub fn detail(ui: &mut Ui, b: &Books, e: &EntryInfo, shell: &mut Shell<'_>) {
|
||||
shell.go(Route::Entry(r));
|
||||
}
|
||||
}
|
||||
if let Some(party) = e.party_id.and_then(|id| b.ledger.get_party(id)) {
|
||||
let several = e.parties.len() > 1;
|
||||
for share in &e.parties {
|
||||
let Some(party) = b.ledger.get_party(share.id) else {
|
||||
continue;
|
||||
};
|
||||
ui.add_space(4.0);
|
||||
if widgets::link(
|
||||
ui,
|
||||
RichText::new(format!(
|
||||
"{} {}",
|
||||
super::parties::party_icon(party),
|
||||
party.label()
|
||||
let mut text = format!("{} {}", super::parties::party_icon(party), party.label());
|
||||
if several {
|
||||
text.push_str(&format!(" · {}", share_text(share)));
|
||||
}
|
||||
if widgets::link(ui, RichText::new(text).color(p.accent))
|
||||
.on_hover_text(format!(
|
||||
"{} this entry is with ({}): open them",
|
||||
if several {
|
||||
"One of the parties"
|
||||
} else {
|
||||
"The party"
|
||||
},
|
||||
super::parties::roles_text(party).to_lowercase()
|
||||
))
|
||||
.color(p.accent),
|
||||
)
|
||||
.on_hover_text(format!(
|
||||
"The party this entry is with ({}): open them",
|
||||
super::parties::roles_text(party).to_lowercase()
|
||||
))
|
||||
.clicked()
|
||||
.clicked()
|
||||
{
|
||||
shell.go(Route::Party(party.id));
|
||||
}
|
||||
}
|
||||
if e.party_id.is_none()
|
||||
if several && e.parties.iter().any(|x| x.resolved.is_none()) {
|
||||
ui.add_space(4.0);
|
||||
ui.label(
|
||||
RichText::new(format!(
|
||||
"{} The parties' shares don't add up to what this entry moved",
|
||||
icon::WARNING
|
||||
))
|
||||
.color(p.negative),
|
||||
);
|
||||
}
|
||||
if e.parties.is_empty()
|
||||
&& let Some(why) = b.ledger.entry_no_party(e.id)
|
||||
{
|
||||
ui.add_space(4.0);
|
||||
@@ -490,3 +505,22 @@ pub fn render(ui: &mut Ui, b: &Books, a: &Attachment, shell: &mut Shell<'_>, wid
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One party's share of an entry, in words: "$50.00 (derived)", "$70.00",
|
||||
/// "$33.34 (33.333%)", or the share alone when it doesn't resolve.
|
||||
pub fn share_text(s: &crate::books::EntryParty) -> String {
|
||||
let how = match s.share {
|
||||
ledger_core::Share::Amount(_) => None,
|
||||
ledger_core::Share::Percent(p) => Some(format!("{}%", p.normalize())),
|
||||
ledger_core::Share::Derived => Some("derived".to_string()),
|
||||
};
|
||||
match (s.resolved, how) {
|
||||
(Some(v), Some(h)) => format!("{} ({h})", fmt::dollars(v.abs())),
|
||||
(Some(v), None) => fmt::dollars(v.abs()),
|
||||
(None, Some(h)) => h,
|
||||
(None, None) => match s.share {
|
||||
ledger_core::Share::Amount(a) => fmt::dollars(a),
|
||||
_ => String::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -473,6 +473,30 @@ pub fn describe(b: &Books, line: &JournalLine) -> Option<Event> {
|
||||
};
|
||||
ev(icon::IDENTIFICATION_CARD, text, pid.map(Route::Party))
|
||||
}
|
||||
"party.tin_set" => {
|
||||
let pid = p.get("id").and_then(|v| v.as_u64());
|
||||
let name = party_name(b, pid);
|
||||
let text = match p.get("facts").filter(|f| !f.is_null()) {
|
||||
Some(f) => {
|
||||
let kind = match f.get("tin_type").and_then(|v| v.as_str()) {
|
||||
Some("ssn") => "SSN",
|
||||
Some("ein") => "EIN",
|
||||
Some("itin") => "ITIN",
|
||||
_ => "TIN",
|
||||
};
|
||||
let l4 = f.get("last4").and_then(|v| v.as_str()).unwrap_or("????");
|
||||
if f.get("purged").is_some_and(|v| !v.is_null()) {
|
||||
format!("Purged {name}'s {kind} (••••{l4}) from the vault")
|
||||
} else {
|
||||
format!(
|
||||
"Taxpayer ID for {name} recorded: {kind} ending {l4}, kept in the vault"
|
||||
)
|
||||
}
|
||||
}
|
||||
None => format!("Taxpayer ID facts for {name} cleared"),
|
||||
};
|
||||
ev(icon::IDENTIFICATION_CARD, text, pid.map(Route::Party))
|
||||
}
|
||||
"party.w4_set" => {
|
||||
let pid = p.get("id").and_then(|v| v.as_u64());
|
||||
let name = party_name(b, pid);
|
||||
@@ -491,6 +515,59 @@ pub fn describe(b: &Books, line: &JournalLine) -> Option<Event> {
|
||||
};
|
||||
ev(icon::IDENTIFICATION_BADGE, text, pid.map(Route::Party))
|
||||
}
|
||||
"entry.parties_set" => {
|
||||
let e = entry_id.unwrap_or(0);
|
||||
let named: Vec<(u64, String)> = p
|
||||
.get("parties")
|
||||
.and_then(|v| v.as_array())
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|x| {
|
||||
let id = x.get("party_id").and_then(|v| v.as_u64())?;
|
||||
let share = match x.get("share") {
|
||||
Some(Value::Object(o)) => o
|
||||
.get("amount")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|a| {
|
||||
rust_decimal::Decimal::from_str_exact(a)
|
||||
.map(fmt::dollars)
|
||||
.unwrap_or_else(|_| a.to_string())
|
||||
})
|
||||
.or_else(|| {
|
||||
o.get("percent")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|pc| format!("{pc}%"))
|
||||
}),
|
||||
_ => None,
|
||||
};
|
||||
let name = party_name(b, Some(id));
|
||||
Some((
|
||||
id,
|
||||
match share {
|
||||
Some(sh) => format!("{name} ({sh})"),
|
||||
None => name,
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect();
|
||||
Some(Event {
|
||||
icon: icon::ADDRESS_BOOK,
|
||||
text: if named.is_empty() {
|
||||
format!("Cleared the parties of #{e}")
|
||||
} else {
|
||||
format!(
|
||||
"Tagged #{e} with {}",
|
||||
named
|
||||
.iter()
|
||||
.map(|(_, n)| n.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" and ")
|
||||
)
|
||||
},
|
||||
route: entry_id.map(Route::Entry),
|
||||
also: named.first().map(|(id, _)| Route::Party(*id)),
|
||||
})
|
||||
}
|
||||
"entry.party_set" => {
|
||||
let pid = p.get("party_id").and_then(|v| v.as_u64());
|
||||
let e = entry_id.unwrap_or(0);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
pub mod accounts;
|
||||
pub mod assets;
|
||||
pub mod capital;
|
||||
pub mod cash;
|
||||
pub mod company;
|
||||
pub mod documents;
|
||||
|
||||
@@ -159,6 +159,21 @@ pub fn masked_tin(last4: &str) -> String {
|
||||
format!("•••-••-{last4}")
|
||||
}
|
||||
|
||||
/// A party's taxpayer-ID facts as the viewer shows them: "TIN on file
|
||||
/// ••••1234 (SSN)", never more than the last four; once purged from the
|
||||
/// vault, "TIN ••••1234 (SSN), purged from the vault".
|
||||
pub fn tin_on_file(t: &ledger_core::TinFacts) -> String {
|
||||
if t.vault_record.is_none() && t.purged.is_some() {
|
||||
format!(
|
||||
"TIN ••••{} ({}), purged from the vault",
|
||||
t.last4,
|
||||
t.tin_type.label()
|
||||
)
|
||||
} else {
|
||||
format!("TIN on file ••••{} ({})", t.last4, t.tin_type.label())
|
||||
}
|
||||
}
|
||||
|
||||
/// Where a party stands on its W-9.
|
||||
///
|
||||
/// ```text
|
||||
@@ -741,6 +756,11 @@ pub fn show_party(ui: &mut Ui, b: &Books, id: PartyId, shell: &mut Shell<'_>) {
|
||||
r.context_menu(|ui| widgets::copy_item(ui, "Copy amount", &value.to_string()));
|
||||
});
|
||||
|
||||
// A member's page is their capital account statement too.
|
||||
if let Some(st) = b.capital_statement(id, span) {
|
||||
super::capital::capital_card(ui, b, &st, party.label(), &mut go);
|
||||
ui.add_space(14.0);
|
||||
}
|
||||
super::columns(ui, 860.0, &[1.0, 1.0], |col, ui| {
|
||||
if col == 0 {
|
||||
what_for_card(ui, b, &flow, &label, &mut go);
|
||||
@@ -882,6 +902,19 @@ fn facts_card(ui: &mut Ui, party: &Party, go: &mut Option<Route>) {
|
||||
&party.id.to_string(),
|
||||
);
|
||||
ui.end_row();
|
||||
k(ui, "Taxpayer ID");
|
||||
match &party.tin {
|
||||
Some(t) => {
|
||||
ui.label(RichText::new(tin_on_file(t)).monospace())
|
||||
.on_hover_text(
|
||||
"Only the last four digits are in the books; the full number is kept in the encrypted TIN vault, for the CPA package.",
|
||||
);
|
||||
}
|
||||
None => {
|
||||
ui.label(RichText::new("not recorded").color(p.faint));
|
||||
}
|
||||
}
|
||||
ui.end_row();
|
||||
if let Some(v) = party.vendor {
|
||||
k(ui, "Vendor record");
|
||||
if widgets::link(ui, RichText::new(format!("vendor {v}")).color(p.accent))
|
||||
@@ -1141,12 +1174,31 @@ fn entries_card(ui: &mut Ui, b: &Books, info: &PartyInfo, go: &mut Option<Route>
|
||||
let text = if draft { p.muted } else { p.text };
|
||||
let accounts: Vec<String> =
|
||||
e.accounts.iter().map(|a| b.account_name(*a)).collect();
|
||||
let sub = accounts.join(" → ");
|
||||
let mut sub = accounts.join(" → ");
|
||||
let kind = if draft {
|
||||
format!("{} · draft", e.origin)
|
||||
} else {
|
||||
e.origin.to_string()
|
||||
};
|
||||
// Shared with other parties: show their part.
|
||||
let mut amount = e.amount;
|
||||
if e.parties.len() > 1
|
||||
&& let Some(mine) = e.parties.iter().find(|x| x.id == info.id)
|
||||
{
|
||||
let others: Vec<&str> = e
|
||||
.parties
|
||||
.iter()
|
||||
.filter(|x| x.id != info.id)
|
||||
.map(|x| x.label.as_str())
|
||||
.collect();
|
||||
sub = format!(
|
||||
"Their share, {} of {}, with {} · {sub}",
|
||||
super::entry::share_text(mine),
|
||||
fmt::dollars(e.amount),
|
||||
others.join(" & ")
|
||||
);
|
||||
amount = mine.resolved.map(|v| v.abs()).unwrap_or(e.amount);
|
||||
}
|
||||
let resp = row(
|
||||
ui,
|
||||
w,
|
||||
@@ -1155,7 +1207,7 @@ fn entries_card(ui: &mut Ui, b: &Books, info: &PartyInfo, go: &mut Option<Route>
|
||||
(format!("#{}", e.id), 64.0, false, p.muted),
|
||||
(e.description.clone(), 0.0, false, text),
|
||||
(kind, 150.0, false, if draft { p.warning } else { p.muted }),
|
||||
(fmt::money(e.amount), amt, true, text),
|
||||
(fmt::money(amount), amt, true, text),
|
||||
],
|
||||
Some((sub.as_str(), p.faint)),
|
||||
false,
|
||||
|
||||
Reference in new issue
Block a user