Skip to main content

Entry

Struct Entry 

Source
pub struct Entry<const P: u8 = 2> { /* private fields */ }
Expand description

An entry draft: what a caller submits to be recorded.

Builders are infallible: an invalid key, kind, account path, dimension or memo is kept as a BuildError (see build_errors) and reported when the draft is recorded. P is inferred from the amounts or fixed by the type (Entry alone means Entry<2>).

use doubleentry::{Entry, amount, currency::EUR, error::Field};
use time::macros::date;

let draft: Entry = Entry::new("invoice-2026-0001", date!(2026 - 03 - 15))
    .debit("Assets:Cash", amount!("1190.00"), EUR)
    .credit("Income:Sales", amount!("1190.00"), EUR)
    .memo("Rechnung 4711");
assert!(draft.fingerprint().is_ok());

let broken: Entry = Entry::new("", date!(2026 - 03 - 15)).debit("Assets::Cash", amount!("1.00"), EUR);
let fields: Vec<Field> = broken.build_errors().iter().map(|e| e.field).collect();
assert_eq!(fields, [Field::Key, Field::Posting(0)]);

A hold action is not a builder choice (holds have their own constructors):

ⓘ
use doubleentry::{Entry, record::HoldAction};
let _ = Entry::<2>::new("k", time::macros::date!(2026 - 01 - 01))
    .hold_action(HoldAction::Open { expires: None });

Implementations§

Source§

impl<const P: u8> Entry<P>

Source

pub const MAX_POSTINGS: usize = 1024

The most postings in one entry.

Source

pub const MAX_DOCUMENTS: usize = 16

The most documents on one entry.

Source

pub const MAX_RATES: usize = 16

The most rates on one entry.

Source

pub const MAX_MEMO_BYTES: usize = 4096

The most bytes of memo text.

Source

pub fn new<K>(key: K, booking_date: Date) -> Self

A draft under an idempotency key, booked on a date.

Source

pub fn value_date(self, date: Date) -> Self

With a value date (defaults to the booking date).

Source

pub fn captured_at(self, at: CaptureTime) -> Self

With the time the caller captured the entry.

Source

pub fn kind<L>(self, kind: L) -> Self
where L: TryInto<Label>, L::Error: Into<ValueError>,

With an entry kind ("invoice", "payment", …).

Source

pub fn memo(self, text: impl Into<String>) -> Self

With a memo: 1–4 096 bytes, tabs and line breaks allowed, no other control character (an invalid memo is rejected as 108 InvalidMemo). Its salt is supplied when the entry is recorded. The memo is the only free-text field (see the module docs).

Source

pub fn memo_salted(self, text: impl Into<String>, salt: MemoSalt) -> Self

With a memo and the salt of its commitment.

Source

pub fn document(self, document: Document) -> Self

With a source document.

Source

pub fn debit<A>(self, account: A, amount: Amount<P>, currency: Currency) -> Self

With a debit posting.

Source

pub fn credit<A>( self, account: A, amount: Amount<P>, currency: Currency, ) -> Self

With a credit posting.

Source

pub fn posting(self, posting: Posting<P>) -> Self

With a posting built by Posting. On a hold (open_hold) the posting is Pending, on every other draft Settled.

Source

pub fn open_hold<K>(key: K, booking_date: Date) -> Self

A hold: a reservation of one amount from one account to another, resolved later exactly once. Add exactly one debit and one credit of the same amount and currency; on this draft they are Pending postings (anything else is refused with code 140). Optionally it expires.

The hold’s id is the id of its entry (Recorded::id), which its resolution names. While it is open, its amount counts against the balance limits of both accounts.

use doubleentry::{Entry, Layer, amount, date, currency::EUR};

let hold: Entry = Entry::open_hold("auth-4711", date!("2026-11-20"))
    .expires(date!("2026-11-30"))
    .debit("Liabilities:Wallets:alice", amount!("100.00"), EUR)
    .credit("Liabilities:Card:clearing", amount!("100.00"), EUR);
assert!(hold.postings().iter().all(|p| p.get_layer() == Layer::Pending));
Source

pub fn expires(self, on: Date) -> Self

The first day a hold may be expired (expire_hold) and the first day it can no longer be posted. It must lie after the hold’s booking date (code 141). Only a hold expires: on any other draft this changes nothing.

use doubleentry::{Entry, date, record::HoldAction};

let hold: Entry = Entry::open_hold("auth-4711", date!("2026-11-20")).expires(date!("2026-11-30"));
assert_eq!(hold.get_hold(), Some(&HoldAction::Open { expires: Some(date!("2026-11-30")) }));
let entry: Entry = Entry::new("sale-1", date!("2026-11-20")).expires(date!("2026-11-30"));
assert_eq!(entry.get_hold(), None);
Source

pub fn post_hold<K>( key: K, booking_date: Date, hold: EntryId, amount: PostAmount<P>, ) -> Self

Posts (captures) the hold hold: all of it, or part with the rest released in the same entry. The draft carries no postings — the rule core derives them from the hold: the two Pending legs released, then the posted amount settled between the hold’s two accounts. A post must be booked before the hold’s expiry date.

use doubleentry::{AccountKind, Entry, Journal, Layer, LedgerId, PostAmount, amount, date, currency::EUR};

let mut books = Journal::new(LedgerId::new("example.com/payments")?);
books.declare_currency(EUR, 2, date!("2026-01-01"))?;
books.open("Liabilities:Wallets:alice", AccountKind::Liability, date!("2026-01-01"))?;
books.open("Liabilities:Card:clearing", AccountKind::Liability, date!("2026-01-01"))?;
books.define_months(2026, 11..=11)?;
let hold = books.record(
    Entry::open_hold("auth-4711", date!("2026-11-20"))
        .debit("Liabilities:Wallets:alice", amount!("100.00"), EUR)
        .credit("Liabilities:Card:clearing", amount!("100.00"), EUR),
)?;
let capture = books.record(Entry::post_hold(
    "capture-4711",
    date!("2026-11-22"),
    hold.id,
    PostAmount::Partial(amount!("60.00")),
))?;
let postings = books.entry(capture.id).map(|entry| entry.parts().postings.clone()).unwrap_or_default();
let legs: Vec<_> = postings.iter().map(|p| (p.layer, p.side, p.amount)).collect();
use doubleentry::Side::{Credit, Debit};
assert_eq!(legs, [
    (Layer::Pending, Credit, amount!("100.00")), // the reservation released
    (Layer::Pending, Debit, amount!("100.00")),
    (Layer::Settled, Debit, amount!("60.00")),   // the capture settled
    (Layer::Settled, Credit, amount!("60.00")),
]);
Source

pub fn void_hold<K>(key: K, booking_date: Date, hold: EntryId) -> Self

Voids (releases) the hold hold on any day from its booking date on, even after its expiry date or after one of its accounts closed. The core derives the two Pending release postings.

use doubleentry::{Entry, EntryId, date, record::HoldAction};

let hold = EntryId::from_bytes([7; 16]);
let void: Entry = Entry::void_hold("void-4711", date!("2026-11-21"), hold);
assert_eq!(void.get_hold(), Some(&HoldAction::Void { hold }));
assert!(void.postings().is_empty());
Source

pub fn expire_hold<K>(key: K, booking_date: Date, hold: EntryId) -> Self

Expires the hold hold: releases it like a void, booked on or after its expiry date. Nothing expires by itself — a scheduler lists the due holds (holds_due) and records an expiry for each.

use doubleentry::{Entry, EntryId, date, record::HoldAction};

let hold = EntryId::from_bytes([7; 16]);
let expiry: Entry = Entry::expire_hold("expire-4711", date!("2026-11-30"), hold);
assert_eq!(expiry.get_hold(), Some(&HoldAction::Expire { hold }));
Source

pub fn reverses(self, target: EntryId) -> Self

Marks the draft as the reversal of an earlier entry.

Source

pub fn rate(self, rate: Rate) -> Self

With an exchange rate; rates are kept sorted by (base, quote) and a second rate for the same pair replaces the first.

Source

pub fn key(&self) -> Option<&IdempotencyKey>

The idempotency key; None when it was invalid.

Source

pub const fn booking_date(&self) -> Date

The booking date.

Source

pub fn get_value_date(&self) -> Date

The value date (the booking date unless set).

Source

pub const fn get_captured_at(&self) -> Option<CaptureTime>

The capture time.

Source

pub const fn get_kind(&self) -> Option<&Label>

The entry kind.

Source

pub fn memo_text(&self) -> Option<&str>

The memo text.

Source

pub fn memo_salt(&self) -> Option<&MemoSalt>

The memo salt, when the caller supplied one.

Source

pub fn documents(&self) -> &[Document]

The documents, in the order added.

Source

pub fn postings(&self) -> &[Posting<P>]

The postings, in the order added.

Source

pub const fn get_reverses(&self) -> Option<EntryId>

The entry this one reverses.

Source

pub const fn get_hold(&self) -> Option<&HoldAction<P>>

The hold action.

Source

pub fn rates(&self) -> &[Rate]

The rates, sorted by (base, quote).

Source

pub fn build_errors(&self) -> &[BuildError]

The builder arguments that failed validation, in the order given.

Source

pub fn encoded_len(&self) -> usize

The length of the entry leaf this draft becomes, without allocating, to check against MAX_LEAF_BYTES before submitting. Meaningful only without build errors. A hold resolution’s derived postings (two to four) are not counted.

Source

pub fn fingerprint(&self) -> Result<Fingerprint, BuildError>

The draft’s Fingerprint: a tagged SHA-256 of the entry body as submitted — accounts as given (path or handle), the memo as text, no salt — so it depends on nothing but the draft.

§Errors

The first BuildError, while any exists.

Source

pub fn id(&self, ledger: &LedgerId) -> Option<EntryId>

The id the entry will have in ledger; None while the key is invalid.

Trait Implementations§

Source§

impl<const P: u8> Clone for Entry<P>

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl<const P: u8> Debug for Entry<P>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl<'de, const P: u8> Deserialize<'de> for Entry<P>

Available on crate feature serde only.
Source§

fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error>

Deserialize this value from the given Serde deserializer. Read more
Source§

impl<const P: u8> Eq for Entry<P>

Source§

impl<const P: u8> Hash for Entry<P>

Source§

fn hash<__H: Hasher>(&self, state: &mut __H)

Feeds this value into the given Hasher. Read more
1.3.0 · Source§

fn hash_slice<H>(data: &[Self], state: &mut H)
where H: Hasher, Self: Sized,

Feeds a slice of this type into the given Hasher. Read more
Source§

impl<const P: u8> PartialEq for Entry<P>

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl<const P: u8> Serialize for Entry<P>

Available on crate feature serde only.
Source§

fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>

Serialize this value into the given Serde serializer. Read more
Source§

impl<const P: u8> StructuralPartialEq for Entry<P>

Auto Trait Implementations§

§

impl<const P: u8> Freeze for Entry<P>

§

impl<const P: u8> RefUnwindSafe for Entry<P>

§

impl<const P: u8> Send for Entry<P>
where Vec<Posting<P>>: Send, Option<HoldAction<P>>: Send,

§

impl<const P: u8> Sync for Entry<P>
where Vec<Posting<P>>: Sync, Option<HoldAction<P>>: Sync,

§

impl<const P: u8> Unpin for Entry<P>

§

impl<const P: u8> UnsafeUnpin for Entry<P>

§

impl<const P: u8> UnwindSafe for Entry<P>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DeserializeOwned for T
where T: for<'de> Deserialize<'de>,

Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Compare self to key and return true if they are equal.
Source§

impl<Q, K> Equivalent<K> for Q
where Q: Eq + ?Sized, K: Borrow<Q> + ?Sized,

Source§

fn equivalent(&self, key: &K) -> bool

Checks if this value is equivalent to the given key. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoEither for T

Source§

fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ

Converts self into a Left variant of Either<Self, Self> if into_left is true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
where F: FnOnce(&Self) -> bool,

Converts self into a Left variant of Either<Self, Self> if into_left(&self) returns true. Converts self into a Right variant of Either<Self, Self> otherwise. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more