pub struct Inventory { /* private fields */ }Expand description
An inventory is a collection of positions.
It tracks all positions for an account and supports booking operations for adding and reducing positions.
§Examples
use rustledger_core::{Inventory, Position, Amount, Cost, BookingMethod};
use rust_decimal_macros::dec;
let mut inv = Inventory::new();
// Add a simple position
inv.add(Position::simple(Amount::new(dec!(100), "USD")));
assert_eq!(inv.units("USD"), dec!(100));
// Add a position with cost
let cost = Cost::new(dec!(150.00), "USD");
inv.add(Position::with_cost(Amount::new(dec!(10), "AAPL"), cost));
assert_eq!(inv.units("AAPL"), dec!(10));Implementations§
Source§impl Inventory
impl Inventory
Sourcepub fn try_reduce(
&self,
units: &Amount,
cost_spec: Option<&CostSpec>,
method: BookingMethod,
) -> Result<BookingResult, BookingError>
pub fn try_reduce( &self, units: &Amount, cost_spec: Option<&CostSpec>, method: BookingMethod, ) -> Result<BookingResult, BookingError>
Try reducing positions without modifying the inventory.
The read-only preview of Self::reduce: returns exactly what
reduce would return — the same matched lots and cost basis on
success, the same error otherwise — without mutating self.
Implemented as reduce on a clone, so it is equivalent BY
CONSTRUCTION. It previously re-implemented every booking method’s
selection logic in a parallel try_* tree, which drifted from the
mutating path in three places (STRICT ambiguity, NONE shorting, {*}
merge dispatch) — the recurring one-logic-two-paths class (#1648,
#1663, #1686). The clone is cheap: positions is an
imbl::Vector, so cloning is O(1) structural sharing and reduce’s
copy-on-write rebuild touches only the clone. The
try_reduce_predicts_reduce property test pins the equivalence.
§Arguments
units- The units to reduce (negative for selling)cost_spec- Optional cost specification for matching lotsmethod- The booking method to use
§Errors
Exactly the errors Self::reduce would return for the same input.
Sourcepub fn merge_average(&mut self)
pub fn merge_average(&mut self)
Collapse every cost-bearing lot of each currency into a single weighted-average-cost lot. Cost-less (cash) positions are left untouched.
This realizes the balance of an AVERAGE-booked account, where all lots of a commodity share one running cost. The journal keeps the real per-lot costs; only this realized view merges them (matching hledger’s pool model). A currency whose lots net to zero is removed; a currency whose lots have mismatched cost currencies is left untouched.
Source§impl Inventory
impl Inventory
Sourcepub fn positions(&self) -> impl Iterator<Item = &Position> + '_
pub fn positions(&self) -> impl Iterator<Item = &Position> + '_
Iterate over all positions.
Previously returned &[Position]; now returns an iterator
because the underlying storage is a tree-based persistent
vector (imbl::Vector) that doesn’t expose a contiguous slice.
Most callers already iterate — for callers that need
random-access / indexed / .len() slice semantics, see
Self::position_list.
Sourcepub fn position_list(&self) -> Vec<&Position>
pub fn position_list(&self) -> Vec<&Position>
Materialize all positions as a Vec<&Position> for slice-style
access (indexing, .len(), .first(), .is_empty()).
Allocates O(N) pointers per call. Callers that only iterate
once should use Self::positions instead — this is for code
paths that need slice semantics.
Sourcepub const fn positions_mut(&mut self) -> &mut Vector<Position>
pub const fn positions_mut(&mut self) -> &mut Vector<Position>
Get mutable access to the underlying positions vector.
Returns &mut imbl::Vector<Position> (was &mut Vec<Position>
before issue #1086). imbl::Vector supports the same surface
for push_back, pop_back, retain, indexed access, and
iteration — but mutations are O(log N) with structural sharing
instead of O(1) amortized.
Sourcepub fn units(&self, currency: &str) -> Decimal
pub fn units(&self, currency: &str) -> Decimal
Get total units of a currency (ignoring cost lots).
This sums all positions of the given currency regardless of cost basis. Uses an internal cache for O(1) lookups.
Sourcepub fn currencies(&self) -> Vec<&str>
pub fn currencies(&self) -> Vec<&str>
Get all currencies in this inventory.
Sourcepub fn is_reduced_by(&self, units: &Amount, scope: ReductionScope) -> bool
pub fn is_reduced_by(&self, units: &Amount, scope: ReductionScope) -> bool
Check if the given units would reduce (not augment) this inventory.
Returns true if there’s a position with the same currency but opposite
sign, meaning these units would reduce the inventory rather than add to it.
When has_cost_spec is true, only positions with a cost basis are
considered for reduction matching. Simple (no-cost) positions are ignored
because they live in a different “cost layer” — a sell-without-cost-spec
that left a negative simple position should not cause a subsequent
cost-bearing augmentation to be misclassified as a reduction.
See: issue #875, beancount#889.
This is used to determine whether a posting is a sale/reduction or a purchase/augmentation.
Sourcepub fn is_booking_reduction(
&self,
units: &Amount,
cost: Option<&CostSpec>,
method: BookingMethod,
) -> bool
pub fn is_booking_reduction( &self, units: &Amount, cost: Option<&CostSpec>, method: BookingMethod, ) -> bool
Whether a posting of units carrying cost would REDUCE this inventory
under method — the single source for the reduction-vs-augmentation
decision shared by the booking engine (BookingEngine::apply) and the
Late validator’s inventory pass.
A posting reduces only when it carries a cost spec (cost.is_some() —
presence of the spec, which includes an empty/unresolved one like {}),
the booking method isn’t NONE (issue #1182 — NONE accumulates every
posting as an augmentation, with no lot matching), and the inventory holds
a cost-bearing position of the opposite sign in the same currency
(Self::is_reduced_by with ReductionScope::CostBearingOnly). This
gate was previously written byte-for-byte in both crates and the #1182 fix
had to be applied twice.
Sourcepub fn book_value(&self, units_currency: &str) -> FxHashMap<Currency, Decimal>
pub fn book_value(&self, units_currency: &str) -> FxHashMap<Currency, Decimal>
Get the total book value (cost basis) for a currency.
Returns the sum of all cost bases for positions of the given currency.
Sourcepub fn add(&mut self, position: Position)
pub fn add(&mut self, position: Position)
Add a position to the inventory.
For positions without cost, this merges with existing positions
of the same currency using O(1) HashMap lookup.
For positions with cost, this adds as a new lot (O(1)). Lot aggregation for display purposes is handled separately at output time (e.g., in the query result formatter).
§TLA+ Specification
Implements AddAmount action from Conservation.tla:
- Invariant:
inventory + totalReduced = totalAdded - After add:
totalAdded' = totalAdded + amount
See: spec/tla/Conservation.tla
Sourcepub fn reduce(
&mut self,
units: &Amount,
cost_spec: Option<&CostSpec>,
method: BookingMethod,
) -> Result<BookingResult, BookingError>
pub fn reduce( &mut self, units: &Amount, cost_spec: Option<&CostSpec>, method: BookingMethod, ) -> Result<BookingResult, BookingError>
Reduce positions from the inventory using the specified booking method.
§Arguments
units- The units to reduce (negative for selling)cost_spec- Optional cost specification for matching lotsmethod- The booking method to use
§Returns
Returns a BookingResult with the matched positions and cost basis,
or a BookingError if the reduction cannot be performed.
§TLA+ Specification
Implements ReduceAmount action from Conservation.tla:
- Invariant:
inventory + totalReduced = totalAdded - After reduce:
totalReduced' = totalReduced + amount - Precondition:
amount <= inventory(elseInsufficientUnitserror)
Lot selection follows these TLA+ specs based on method:
Fifo:FIFOCorrect.tla- Oldest lots first (selected_date <= all other dates)Lifo:LIFOCorrect.tla- Newest lots first (selected_date >= all other dates)Hifo:HIFOCorrect.tla- Highest cost first (selected_cost >= all other costs)
See: spec/tla/Conservation.tla, spec/tla/FIFOCorrect.tla, etc.
Trait Implementations§
Source§impl<'de> Deserialize<'de> for Inventory
impl<'de> Deserialize<'de> for Inventory
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
impl Eq for Inventory
Source§impl FromIterator<Position> for Inventory
impl FromIterator<Position> for Inventory
Auto Trait Implementations§
impl Freeze for Inventory
impl RefUnwindSafe for Inventory
impl Send for Inventory
impl Sync for Inventory
impl Unpin for Inventory
impl UnsafeUnpin for Inventory
impl UnwindSafe for Inventory
Blanket Implementations§
Source§impl<T> ArchivePointee for T
impl<T> ArchivePointee for T
Source§type ArchivedMetadata = ()
type ArchivedMetadata = ()
Source§fn pointer_metadata(
_: &<T as ArchivePointee>::ArchivedMetadata,
) -> <T as Pointee>::Metadata
fn pointer_metadata( _: &<T as ArchivePointee>::ArchivedMetadata, ) -> <T as Pointee>::Metadata
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> DeserializeOwned for Twhere
T: for<'de> Deserialize<'de>,
Source§impl<Q, K> Equivalent<K> for Q
impl<Q, K> Equivalent<K> for Q
Source§impl<T> LayoutRaw for T
impl<T> LayoutRaw for T
Source§fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
fn layout_raw(_: <T as Pointee>::Metadata) -> Result<Layout, LayoutError>
Source§impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
impl<T, N1, N2> Niching<NichedOption<T, N1>> for N2
Source§unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
unsafe fn is_niched(niched: *const NichedOption<T, N1>) -> bool
Source§fn resolve_niched(out: Place<NichedOption<T, N1>>)
fn resolve_niched(out: Place<NichedOption<T, N1>>)
out indicating that a T is niched.