#![allow(dead_code)]
use doubleentry::store::Store;
use doubleentry::{
AccountKind, AccountSpec, Amount, Currency, Document, Entry, EntryId, Journal, Ledger,
LedgerId, MemoSalt, PostAmount, Posting, PostingRef, Rate, Recorded,
currency::{EUR, JPY, USD},
date,
};
#[derive(Debug, Clone)]
pub struct Fixture {
pub month_end_sale: Recorded,
pub late_sale: Recorded,
pub backdated_sale: Recorded,
pub reversal: Recorded,
pub invoice: Recorded,
pub payment: Recorded,
pub clearing: u64,
pub reset: u64,
pub erased: EntryId,
pub travel: [Recorded; 2],
pub holds: [Recorded; 4],
}
fn eur(text: &str) -> Amount {
text.parse().unwrap()
}
fn sale(key: &str, booked: doubleentry::Date, amount: Amount, currency: Currency) -> Entry {
Entry::new(key, booked)
.debit("Assets:Bank", amount, currency)
.credit("Income:Sales", amount, currency)
}
pub async fn fixture<S: Store<2>>(store: S) -> (Ledger<S>, Fixture) {
let books = Ledger::new(store, LedgerId::new("example.com/acme").unwrap())
.await
.unwrap()
.as_actor("setup")
.with_salt_source(|| MemoSalt([7; 16]));
let on = date!("2026-01-01");
for (code, exponent) in [(EUR, 2), (USD, 2), (JPY, 0)] {
books.declare_currency(code, exponent, on).await.unwrap();
}
for (path, kind) in [
("Assets:Bank", AccountKind::Asset),
("Assets:Cash", AccountKind::Asset),
("Assets:Receivables", AccountKind::Asset),
("Income:Sales", AccountKind::Income),
("Liabilities:VAT", AccountKind::Liability),
("Expenses:Rent", AccountKind::Expense),
("Equity:Retained", AccountKind::Equity),
] {
books.open(path, kind, on).await.unwrap();
}
books
.register(AccountSpec::new("Expenses:Travel", AccountKind::Expense, on).class("operating"))
.await
.unwrap();
books
.set_class("Expenses:Travel", Some("capital"), date!("2026-06-01"))
.await
.unwrap();
books.define_months(2026, 1..=12).await.unwrap();
let record = |entry: Entry| async { books.record(entry).await.unwrap() };
record(
Entry::new("jan-sale", date!("2026-01-10"))
.debit("Assets:Bank", eur("1000.00"), EUR)
.posting(
Posting::credit("Income:Sales", eur("1000.00"), EUR).dimension("cost_centre", "A"),
)
.memo("january"),
)
.await;
record(
Entry::new("feb-rent", date!("2026-02-03"))
.value_date(date!("2026-02-05"))
.posting(
Posting::debit("Expenses:Rent", eur("300.00"), EUR).dimension("cost_centre", "B"),
)
.credit("Assets:Bank", eur("300.00"), EUR),
)
.await;
books.seal_period("2026-01").await.unwrap();
books.seal_period("2026-02").await.unwrap();
let month_end_sale = record(
sale("mar-31", date!("2026-03-31"), eur("100.00"), EUR).value_date(date!("2026-04-02")),
)
.await;
let late_sale = record(
Entry::new("mar-20", date!("2026-03-20"))
.debit("Assets:Bank", eur("50.00"), EUR)
.posting(
Posting::credit("Income:Sales", eur("50.00"), EUR).dimension("cost_centre", "A"),
),
)
.await;
let backdated_sale = record(
Entry::new("mar-15", date!("2026-03-15"))
.debit("Assets:Bank", eur("20.00"), EUR)
.posting(
Posting::credit("Income:Sales", eur("20.00"), EUR).dimension("cost_centre", "B"),
),
)
.await;
let invoice = record(
Entry::new("invoice-1", date!("2026-03-01"))
.kind("invoice")
.debit("Assets:Receivables", eur("119.00"), EUR)
.credit("Income:Sales", eur("100.00"), EUR)
.credit("Liabilities:VAT", eur("19.00"), EUR)
.document(
Document::new("invoice", "RE-2026-0001")
.unwrap()
.dated(date!("2026-03-01"))
.with_sha256([0x5a; 32]),
),
)
.await;
record(
sale("usd-sale", date!("2026-03-05"), eur("50.00"), USD)
.rate(Rate::parse(EUR, USD, "1.0873").unwrap()),
)
.await;
record(sale(
"jpy-sale",
date!("2026-03-06"),
"500".parse().unwrap(),
JPY,
))
.await;
let erased = record(
Entry::new("erase-me", date!("2026-03-07"))
.debit("Assets:Cash", eur("1.00"), EUR)
.credit("Income:Sales", eur("1.00"), EUR)
.memo("a customer's name"),
)
.await;
books.erase_memo(erased.id).await.unwrap();
let travel_march = record(
Entry::new("travel-mar", date!("2026-03-10"))
.debit("Expenses:Travel", eur("40.00"), EUR)
.credit("Assets:Cash", eur("40.00"), EUR),
)
.await;
let reversal = books
.reversal(late_sale.id, "mar-20-reversal", date!("2026-03-25"))
.await
.unwrap();
let reversal = record(reversal).await;
let payment = record(
Entry::new("payment-1", date!("2026-04-01"))
.debit("Assets:Bank", eur("100.00"), EUR)
.credit("Assets:Receivables", eur("100.00"), EUR),
)
.await;
let clearing = books
.clear(
"clear-1",
PostingRef::at(invoice.id, 0),
PostingRef::at(payment.id, 1),
eur("100.00"),
date!("2026-04-01"),
)
.await
.unwrap();
let reset = books
.reset_clearing("reset-1", clearing, date!("2026-04-10"))
.await
.unwrap();
let travel_july = record(
Entry::new("travel-jul", date!("2026-07-10"))
.debit("Expenses:Travel", eur("60.00"), EUR)
.credit("Assets:Cash", eur("60.00"), EUR),
)
.await;
let withdrawal = record(
Entry::open_hold("hold-1", date!("2026-08-03"))
.expires(date!("2026-08-31"))
.debit("Assets:Cash", eur("80.00"), EUR)
.credit("Assets:Bank", eur("80.00"), EUR),
)
.await;
let capture = record(Entry::post_hold(
"hold-1-post",
date!("2026-08-05"),
withdrawal.id,
PostAmount::Partial(eur("30.00")),
))
.await;
let second = record(
Entry::open_hold("hold-2", date!("2026-08-06"))
.debit("Assets:Cash", eur("20.00"), EUR)
.credit("Assets:Bank", eur("20.00"), EUR),
)
.await;
let void = record(Entry::void_hold(
"hold-2-void",
date!("2026-08-07"),
second.id,
))
.await;
let fixture = Fixture {
month_end_sale,
late_sale,
backdated_sale,
reversal,
invoice,
payment,
clearing,
reset,
erased: erased.id,
travel: [travel_march, travel_july],
holds: [withdrawal, capture, second, void],
};
(books, fixture)
}
pub async fn journal() -> (Journal, Fixture) {
let (books, fixture) = fixture(doubleentry::store::MemoryStore::new()).await;
(books.to_journal().await.unwrap(), fixture)
}