Assignment

NAME

App::Moneymoor::Gateway::Assignment - SQL gateway for the money you give categories each budget period.

SYNOPSIS


use App::Moneymoor::Gateway::Assignment;

# The scheme decides which keys are legal; it defaults to the
# calendar month, whose starts are always 'YYYY-MM-01'.
my $gw = App::Moneymoor::Gateway::Assignment.new(:$db);

$gw.set('2026-03-01', $groceries, 40000);      # insert
$gw.set('2026-03-01', $groceries, 45000);      # update — one row/pair
say $gw.get('2026-03-01', $groceries).amount;  # 45000

# Budgeting December in March is legal and deliberate:
$gw.set('2026-12-01', $council-tax, 30000);

# Robbing Peter to pay Paul, atomically:
$gw.move-money('2026-03-01', $groceries, $dining, 5000);
say $gw.get('2026-03-01', $groceries).amount;  # 40000
say $gw.get('2026-03-01', $dining).amount;     # 5000

my @march = $gw.find-by-period('2026-03-01');
$gw.delete('2026-03-01', $dining);             # same as setting 0, but
                                               # leaves no row behind

# A pay-day scheme moves the legal keys, and nothing else:
my $pay = App::Moneymoor::Gateway::Assignment.new(
    :$db, scheme => App::Moneymoor::Util::Period.monthly(anchor-day => 14));
$pay.set('2026-03-14', $groceries, 40000);     # fine
$pay.set('2026-03-01', $groceries, 40000);     # Failure: not a start

DESCRIPTION

An assignment is a set, not an increment: there is one row per (period, category), guaranteed by a unique index, and set upserts. That is what makes "assign £400 to Groceries in March" idempotent no matter how many times a UI fires it.

move-money is the other primitive — two upserts in one run-txn, so the pair can never half-apply and invent or destroy money. It adjusts relatively (subtract from one, add to the other), because that is what moving money means when both categories already have assignments.

Negative amounts are legal. Pulling £50 back out of a category that had nothing assigned this period leaves -5000 assigned, and the engine reads that exactly as written: the category has £50 less, Ready to Assign has £50 more.

Assigning to the rta category is refused. Ready to Assign is the pool assignments come from; letting money be assigned to it would make rule 4 circular.

PERIODS ARE VALIDATED AS STARTS

Every method that takes a period key checks two things and fails unless both hold: the key is a well-formed YYYY-MM-DD date, and it is the start of a period under this gateway's scheme ($scheme.period-of($period) eq $period).

The first check is the one the old YYYY-MM validation did, and for the same reason: a typo'd key does not merely misplace the money, it extends the derived range, so '20226-03-01' would ask the engine to derive eighteen thousand years of budget.

The second is stronger, and matters more. A key that is well formed but is not a start — '2026-03-15' under the calendar month, or a '2026-03' row from before the period migration — creates a phantom bucket. Nothing rejects it downstream; the derivation happily sums it alongside the real periods, so the money is neither lost nor visible, and the budget stops adding up in a way no screen explains. Storing such a key is worse than storing a typo, because a typo announces itself.

ATTRIBUTES

  • db — required App::Moneymoor::DB.

  • scheme — the App::Moneymoor::Util::Period whose starts are legal keys. Defaults to monthly/1, the calendar month, which is what every budget written so far uses.

METHODS

  • set(Str:D $period, Int:D $category-id, Int:D $amount -- Model::Assignment)>

  • adjust(Str:D $period, Int:D $category-id, Int:D $delta -- Model::Assignment)> — relative change; the primitive move-money is built from.

  • get(Str:D $period, Int:D $category-id -- Model::Assignment)> — type object when nothing has been assigned.

  • amount-for(Str:D $period, Int:D $category-id -- Int)> — 0 when nothing has been assigned.

  • find-all(-- Array)> — ordered (period_start, id).

  • find-by-period(Str:D $period -- Array)>

  • find-by-category(Int:D $category-id -- Array)>

  • move-money(Str:D $period, Int:D $from-id, Int:D $to-id, Int:D $amount)

  • delete(Str:D $period, Int:D $category-id)

App::Moneymoor v0.4.2

YNAB-style envelope budgeting: a derivation engine

Authors

  • Matt Doughty

License

Artistic-2.0

Dependencies

DBIish:ver<0.6.7>:auth<zef:raku-community-modules>Notcurses::Native:ver<0.6.5+>:auth<zef:apogee>Selkie:ver<0.16.0+>:auth<zef:apogee>JSON::Fast:ver<0.19>:auth<cpan:TIMOTIMO>MacOS::NativeLib:ver<0.0.6>:auth<zef:lizmat>

Test Dependencies

Provides

  • App::Moneymoor
  • App::Moneymoor::Config
  • App::Moneymoor::DB
  • App::Moneymoor::Gateway::Account
  • App::Moneymoor::Gateway::Assignment
  • App::Moneymoor::Gateway::Category
  • App::Moneymoor::Gateway::Payee
  • App::Moneymoor::Gateway::Transaction
  • App::Moneymoor::Handlers::Boot
  • App::Moneymoor::Model::Account
  • App::Moneymoor::Model::Assignment
  • App::Moneymoor::Model::Category
  • App::Moneymoor::Model::CategoryGroup
  • App::Moneymoor::Model::Payee
  • App::Moneymoor::Model::Split
  • App::Moneymoor::Model::Transaction
  • App::Moneymoor::Screen::Accounts
  • App::Moneymoor::Screen::Budget
  • App::Moneymoor::Screen::Login
  • App::Moneymoor::Screen::Main
  • App::Moneymoor::Screen::Main::Keybinds
  • App::Moneymoor::Screen::Main::Modals
  • App::Moneymoor::Screen::Main::Subscriptions
  • App::Moneymoor::Screen::Reports
  • App::Moneymoor::Service::Budget
  • App::Moneymoor::Service::Icons
  • App::Moneymoor::Service::Target
  • App::Moneymoor::Service::Workspace
  • App::Moneymoor::StoreHandlers
  • App::Moneymoor::Theme
  • App::Moneymoor::Theme::Catppuccin
  • App::Moneymoor::Theme::Dracula
  • App::Moneymoor::Theme::Everforest
  • App::Moneymoor::Theme::Gruvbox
  • App::Moneymoor::Theme::Kanagawa
  • App::Moneymoor::Theme::Monokai
  • App::Moneymoor::Theme::Nord
  • App::Moneymoor::Theme::OneDark
  • App::Moneymoor::Theme::RosePine
  • App::Moneymoor::Theme::Solarized
  • App::Moneymoor::Theme::TokyoNight
  • App::Moneymoor::Themes
  • App::Moneymoor::UI
  • App::Moneymoor::Util::Money
  • App::Moneymoor::Util::Period
  • App::Moneymoor::View::BudgetRow
  • App::Moneymoor::View::EmptyState
  • App::Moneymoor::View::HintBar
  • App::Moneymoor::View::InspectorPane
  • App::Moneymoor::View::ModalChrome
  • App::Moneymoor::View::RegisterRow
  • App::Moneymoor::View::ReportRow
  • App::Moneymoor::Widget::BannerBar
  • App::Moneymoor::Widget::BootProgressModal

The Camelia image is copyright 2009 by Larry Wall. "Raku" is a trademark of the Yet Another Society. All rights reserved.

Built with Podlite — the markup and publishing tools behind this site.