use core::fmt;
use core::fmt::Display;
use core::fmt::Formatter;
use core::fmt::Write as _;
use core::hash::Hash;
use beancount_price_spec::PriceSpec;
use tap::Tap as _;
use time::format_description::well_known::Rfc3339;
use time::Date;
use time::OffsetDateTime;
use crate::Acc;
use crate::Account;
use crate::Amount;
use crate::CostSpec;
use crate::Link;
use crate::LinkSet;
use crate::MetadataKey;
use crate::MetadataMap;
use crate::MetadataValue;
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Posting {
pub flag: Option<Flag>,
pub account: Account,
pub amount: Option<Amount>,
pub cost: Option<CostSpec>,
pub price: Option<PriceSpec>,
pub meta: MetadataMap,
}
impl Posting {
pub fn on(account: impl Into<Account>) -> Self {
let account = account.into();
let (flag, amount, cost, price, meta) = Default::default();
Self {
flag,
account,
amount,
cost,
price,
meta,
}
}
}
impl Posting {
#[inline]
pub fn add_meta(
&mut self,
key: impl Into<MetadataKey>,
value: impl Into<MetadataValue>,
) -> &mut Self {
self.meta.insert(key.into(), value.into());
self
}
#[inline]
pub fn clear_amount(&mut self) -> &mut Self {
self.amount = None;
self
}
#[inline]
pub fn clear_cost(&mut self) -> &mut Self {
self.cost = None;
self
}
#[inline]
pub fn clear_flag(&mut self) -> &mut Self {
self.flag = None;
self
}
#[inline]
pub fn clear_meta(&mut self) -> &mut Self {
self.meta.clear();
self
}
#[inline]
pub fn clear_price(&mut self) -> &mut Self {
self.price = None;
self
}
#[inline]
pub fn complete(&mut self) -> &mut Self {
self.set_flag(Flag::Complete)
}
#[inline]
pub fn incomplete(&mut self) -> &mut Self {
self.set_flag(Flag::Incomplete)
}
#[inline]
pub fn set_account(&mut self, account: impl Into<Account>) -> &mut Self {
self.account = account.into();
self
}
#[inline]
pub fn set_amount(&mut self, amount: Amount) -> &mut Self {
self.amount = Some(amount);
self
}
#[inline]
pub fn set_cost(&mut self, cost: impl Into<CostSpec>) -> &mut Self {
self.cost = Some(cost.into());
self
}
#[inline]
pub fn set_flag(&mut self, flag: Flag) -> &mut Self {
self.flag = Some(flag);
self
}
#[inline]
pub fn set_meta(&mut self, meta: impl Into<MetadataMap>) -> &mut Self {
self.meta = meta.into();
self
}
#[inline]
pub fn set_price(&mut self, price: impl Into<PriceSpec>) -> &mut Self {
self.price = Some(price.into());
self
}
}
impl Display for Posting {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let Self {
flag,
account,
amount,
cost,
price,
meta,
} = self;
if let Some(flag) = flag {
write!(f, "{flag} ")?;
}
write!(f, "{account}")?;
if let Some(amount) = amount {
write!(f, " {amount}")?;
if let Some(cost) = cost {
write!(f, " {cost}")?;
}
if let Some(price) = price {
write!(f, " {price}")?;
}
}
for (key, value) in meta {
write!(f, "\n {key}: {value}")?;
}
Ok(())
}
}
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
pub struct Transaction {
pub date: Date,
pub flag: Flag,
pub payee: Option<String>,
pub narration: Option<String>,
pub links: LinkSet,
pub meta: MetadataMap,
pub postings: Vec<Posting>,
}
impl Transaction {
#[must_use]
pub fn on(date: Date) -> Self {
let (flag, links, meta, payee, narration, postings) = Default::default();
Self {
date,
flag,
payee,
narration,
links,
meta,
postings,
}
}
}
impl Transaction {
#[inline]
pub fn add_posting(&mut self, posting: Posting) -> &mut Self {
self.postings.push(posting);
self
}
#[inline]
pub fn add_meta(
&mut self,
key: impl Into<MetadataKey>,
value: impl Into<MetadataValue>,
) -> &mut Self {
self.meta.insert(key.into(), value.into());
self
}
#[inline]
pub fn add_link(&mut self, link: impl Into<Link>) -> &mut Self {
self.links.insert(link.into());
self
}
#[inline]
pub fn build_posting(
&mut self,
on: impl Into<Account>,
block: impl FnOnce(&mut Posting),
) -> &mut Self {
self.add_posting(Posting::on(on).tap_mut(block))
}
#[inline]
pub fn clear_meta(&mut self) -> &mut Self {
self.meta.clear();
self
}
#[inline]
pub fn clear_narration(&mut self) -> &mut Self {
self.narration = None;
self
}
#[inline]
pub fn clear_payee(&mut self) -> &mut Self {
self.payee = None;
self
}
#[inline]
pub fn clear_postings(&mut self) -> &mut Self {
self.postings.clear();
self
}
#[inline]
pub fn complete(&mut self) -> &mut Self {
self.set_flag(Flag::Complete)
}
#[inline]
pub fn incomplete(&mut self) -> &mut Self {
self.set_flag(Flag::Incomplete)
}
#[inline]
pub fn set_date(&mut self, date: Date) -> &mut Self {
self.date = date;
self
}
#[inline]
pub fn set_flag(&mut self, flag: Flag) -> &mut Self {
self.flag = flag;
self
}
#[inline]
pub fn set_meta(&mut self, meta: impl Into<MetadataMap>) -> &mut Self {
self.meta = meta.into();
self
}
#[inline]
pub fn set_narration(&mut self, narration: impl Into<String>) -> &mut Self {
self.narration = Some(narration.into());
self
}
#[inline]
pub fn set_payee(&mut self, payee: impl Into<String>) -> &mut Self {
self.payee = Some(payee.into());
self
}
#[inline]
pub fn set_postings(&mut self, postings: impl Into<Vec<Posting>>) -> &mut Self {
self.postings = postings.into();
self
}
}
impl Transaction {
#[must_use]
pub fn main_account(&self) -> Option<&Acc> {
self.postings
.first()
.map(|posting| posting.account.as_ref())
}
#[inline]
pub fn timestamp(&self) -> Option<OffsetDateTime> {
self.meta
.get("timestamp")
.and_then(MetadataValue::as_str)
.and_then(|timestamp| OffsetDateTime::parse(timestamp, &Rfc3339).ok())
}
}
impl Display for Transaction {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let Self {
date,
flag,
payee,
links,
meta,
narration,
postings,
} = self;
write!(f, "{date} {flag}")?;
match (payee, narration) {
(None, None) => {}
(None, Some(narration)) => write!(f, r#" "{narration}""#)?,
(Some(payee), None) => write!(f, r#" "{payee}" """#)?,
(Some(payee), Some(narration)) => write!(f, r#" "{payee}" "{narration}""#)?,
}
for link in links {
write!(f, "\n {link}")?;
}
for (key, value) in meta {
write!(f, "\n {key}: {value}")?;
}
for posting in postings {
write!(f, "\n {posting}")?;
}
Ok(())
}
}
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub enum Flag {
#[default]
Complete,
Incomplete,
}
impl Display for Flag {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.write_char(match self {
Self::Complete => '*',
Self::Incomplete => '!',
})
}
}