revbank/lib/RevBank/Cart.pm
Juerd Waalboer 807d255b53 Expose deltas
In preparation for future plugin
2024-11-17 01:15:18 +01:00

187 lines
4.6 KiB
Perl

package RevBank::Cart;
use v5.32;
use warnings;
use experimental 'signatures'; # stable since v5.36
use Carp ();
use List::Util ();
use RevBank::Global;
use RevBank::Users;
use RevBank::FileIO;
use RevBank::Cart::Entry;
{
package RevBank::Cart::CheckoutProhibited;
sub new($class, $reason) { return bless \$reason, $class; }
sub reason($self) { return $$self; }
}
sub new($class) {
return bless { entries => [] }, $class;
}
sub add_entry($self, $entry) {
RevBank::Plugins::call_hooks("add_entry", $self, $entry);
push @{ $self->{entries} }, $entry;
$self->{changed}++;
$self->select($entry);
RevBank::Plugins::call_hooks("added_entry", $self, $entry);
return $entry;
}
sub add($self, $amount, $description, $data = {}) {
ref $data or Carp::croak "Non-hash data argument";
return $self->add_entry(RevBank::Cart::Entry->new($amount, $description, $data));
}
sub select($self, $entry) {
return $self->{selected_entry} = $entry;
}
sub selected($self) {
return undef if not @{ $self->{entries} };
for my $entry (@{ $self->{entries} }) {
return $entry if $entry == $self->{selected_entry};
}
return $self->select( $self->{entries}->[-1] );
}
sub delete($self, $entry) {
my $entries = $self->{entries};
my $oldnum = @$entries;
@$entries = grep $_ != $entry, @$entries;
$self->{changed}++;
return $oldnum - @$entries;
}
sub empty($self) {
$self->{entries} = [];
$self->{changed}++;
}
sub display($self, $prefix = "") {
say "$prefix$_" for map $_->as_printable, @{ $self->{entries} };
}
sub size($self) {
return scalar @{ $self->{entries} };
}
sub prohibit_checkout($self, $bool, $reason) {
if ($bool) {
$self->{prohibited} = $reason;
} else {
delete $self->{prohibited};
}
}
sub deltas($self, $user) {
my %deltas = ($user => RevBank::Amount->new(0));
for my $entry (@{ $self->{entries} }) {
$deltas{$_->{user}} += $_->{amount} * $entry->quantity
for $entry, $entry->contras;
}
return \%deltas;
}
sub checkout($self, $user) {
if ($self->{prohibited}) {
die RevBank::Cart::CheckoutProhibited->new(
"Cannot complete transaction: $self->{prohibited}"
);
}
if ($self->entries('refuse_checkout')) {
$self->display;
die "Refusing to finalize deficient transaction";
}
$user = RevBank::Users::assert_user($user);
my $entries = $self->{entries};
for my $entry (@$entries) {
$entry->sanity_check;
$entry->user($user);
}
RevBank::FileIO::with_lock {
my $fn = ".revbank.nextid";
my $transaction_id = eval { RevBank::FileIO::slurp($fn) };
my $legacy_id = 0;
if (defined $transaction_id) {
chomp $transaction_id;
if ($transaction_id eq "LEGACY") {
$legacy_id = 1;
$transaction_id = time() - 1300000000;;
}
} else {
warn "Could not read $fn; using timestamp as first transaction ID.\n";
$transaction_id = time() - 1300000000;
}
RevBank::Plugins::call_hooks("checkout_prepare", $self, $user, $transaction_id)
or die "Refusing to finalize after failed checkout_prepare";
for my $entry (@$entries) {
$entry->sanity_check;
$entry->user($user) if not $entry->user;
}
RevBank::FileIO::spurt($fn, ++(my $next_id = $transaction_id)) unless $legacy_id;
RevBank::Plugins::call_hooks("checkout", $self, $user, $transaction_id);
my $deltas = $self->deltas($user);
for my $account (reverse sort keys %$deltas) {
# The reverse sort is a lazy way to make the "-" accounts come last,
# which looks nicer with the "cash" plugin.
RevBank::Users::update($account, $deltas->{$account}, $transaction_id)
if $deltas->{$account} != 0;
}
RevBank::Plugins::call_hooks("checkout_done", $self, $user, $transaction_id);
sleep 1; # look busy
$self->empty;
};
}
sub entries($self, $attribute = undef) {
my @entries = @{ $self->{entries} };
return grep $_->has_attribute($attribute), @entries if defined $attribute;
return @entries;
}
sub changed($self, $keep = 0) {
my $changed = 0;
for my $entry ($self->entries('changed')) {
$entry->attribute('changed', undef) unless $keep;
$changed = 1;
}
$changed = 1 if $self->{changed};
delete $self->{changed} unless $keep;
return $changed;
}
sub sum($self) {
return List::Util::sum(map $_->{amount} * $_->quantity, @{ $self->{entries} });
}
1;