Transaction
NAME
App::Moneymoor::Model::Transaction - money entering or leaving one account on one day.
SYNOPSIS
use App::Moneymoor::Model::Transaction;
# £42.50 of groceries on the Visa (outflow: negative):
my $spend = App::Moneymoor::Model::Transaction.new(
account-id => $visa-id,
date => '2026-03-14',
payee-id => $tesco-id,
amount => -4250,
);
# £1,800 of salary into the current account (inflow: positive):
my $pay = App::Moneymoor::Model::Transaction.new(
account-id => $current-id,
date => '2026-03-25',
amount => 180000,
);
my $t = App::Moneymoor::Model::Transaction.new-from-row(%row);
say $t.is-transfer; # True when it has a peer leg
say $t.is-outflow; # amount < 0
DESCRIPTION
amount is integer pence, signed from the account's point of
view: an inflow is positive, an outflow is negative. That single
convention is what makes an account balance a plain sum, and it holds
for every account type ā spending Ā£42.50 on a credit card is
-4250, which drives the card's balance further negative (i.e.
deeper into debt).
A transaction is either categorized or a transfer:
Categorized: it owns one or more
splitswhose amounts sum toamount. A single-category transaction is just a transaction with one split ā there is no separate "simple" shape to special case.Transfer:
transfer-peer-idpoints at the other leg, which is a separate transaction on the other account with the negated amount and the same date. Transfers between two on-budget accounts carry no splits at all: the money has not left the budget, so no envelope should move. (The one derived exception is a transfer involving a credit account, which moves that card's payment envelope ā seeApp::Moneymoor::Service::Budget.) A transfer to or from a tracking account is different: money really is leaving or entering the budget, so the on-budget leg is categorized and carries splits.
cleared is the reconciliation state: uncleared (you entered it),
cleared (you saw it on the statement), reconciled (locked as
part of a finished reconciliation). v0.1 stores it and reports
cleared / uncleared balances; the reconciliation workflow arrives with
the UI.
date is YYYY-MM-DD ā no timezone, no time of day. A transaction
happens on a day, and a day is a calendar fact, not an instant.
It is not bucketed here. Which budget period a date falls in
depends on the budget's period scheme (a calendar month, a month
anchored on payday, a four-weekly pay window), and this model does not
know the scheme and should not: a bucket derived from a date by a
scheme-ignorant model is right only for the default scheme and
silently wrong for every other one. Service::Budget asks
App::Moneymoor::Util::Period the question, once, with the scheme in
hand.
ATTRIBUTES
idā primary key; absent on a not-yet-inserted row.account-idā required FK toaccounts.id.dateā requiredYYYY-MM-DD.payee-idā FK topayees.id; null for transfers.memoā free-form, default empty string.amountā required signed integer pence.clearedāuncleared/cleared/reconciled.transfer-peer-idā FK to the other leg'stransactions.id.created-atā gateway-managed timestamp.
METHODS
new-from-row(%row)ā build from a DBIish row hash.is-inflow/is-outflowā amount sign predicates (a zero amount is neither).is-transferā has a peer leg.is-clearedā cleared or reconciled (the "has the bank seen it" question).is-reconciledā strictly reconciled.