
In hindsight, it was a bad idea to allow manipulating the cart (entries) in hook_checkout, because that hook is used by the `log` plugin. You now get unused entries in the log. Although that plugin should maybe have used hook_checkout_done, existing log file readers (including scripts) and custom plugins may depend on the CHECKOUT items in the log being before the BALANCE items.
135 lines
3.4 KiB
Perl
135 lines
3.4 KiB
Perl
package RevBank::Cart;
|
|
|
|
use v5.28;
|
|
use warnings;
|
|
use feature qw(signatures);
|
|
no warnings qw(experimental::signatures);
|
|
|
|
use Carp ();
|
|
use List::Util ();
|
|
use RevBank::Global;
|
|
use RevBank::Users;
|
|
use RevBank::FileIO;
|
|
use RevBank::Cart::Entry;
|
|
|
|
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}++;
|
|
RevBank::Plugins::call_hooks("added_entry", $self, $entry);
|
|
|
|
return $entry;
|
|
}
|
|
|
|
sub add($self, $amount, $description, $data = {}) {
|
|
Carp::croak "Non-hash data argument; possibly a deprecated call style"
|
|
if not ref $data;
|
|
|
|
# Old pre-v3 call styles:
|
|
# ->add(undef, ...) => just remove the "undef,"
|
|
# ->add($user, ...) => use $cart->add(...)->add_contra($user, ...)
|
|
# ->add($entry) => use $cart->add_entry($entry)
|
|
|
|
return $self->add_entry(RevBank::Cart::Entry->new($amount, $description, $data));
|
|
}
|
|
|
|
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 checkout($self, $user) {
|
|
if ($self->entries('refuse_checkout')) {
|
|
warn "Refusing to finalize deficient transaction.\n";
|
|
$self->display;
|
|
return;
|
|
}
|
|
|
|
$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 $transaction_id = time() - 1300000000;
|
|
|
|
RevBank::Plugins::call_hooks("checkout_prepare", $self, $user, $transaction_id);
|
|
for my $entry (@$entries) {
|
|
$entry->sanity_check;
|
|
$entry->user($user) if not $entry->user;
|
|
}
|
|
|
|
RevBank::Plugins::call_hooks("checkout", $self, $user, $transaction_id);
|
|
|
|
my %deltas = ($user => RevBank::Amount->new(0));
|
|
|
|
for my $entry (@$entries) {
|
|
$deltas{$_->{user}} += $_->{amount} * $entry->quantity
|
|
for $entry, $entry->contras;
|
|
}
|
|
|
|
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 (and ensure new id for next transaction :))
|
|
};
|
|
|
|
$self->empty;
|
|
|
|
return 1;
|
|
}
|
|
|
|
sub entries($self, $attribute = undef) {
|
|
my @entries = @{ $self->{entries} };
|
|
return grep $_->has_attribute($attribute), @entries if defined $attribute;
|
|
return @entries;
|
|
}
|
|
|
|
sub changed($self) {
|
|
my $changed = 0;
|
|
for my $entry ($self->entries('changed')) {
|
|
$entry->attribute('changed', undef);
|
|
$changed = 1;
|
|
}
|
|
$changed = 1 if delete $self->{changed};
|
|
return $changed;
|
|
}
|
|
|
|
sub sum($self) {
|
|
return List::Util::sum(map $_->{amount} * $_->quantity, @{ $self->{entries} });
|
|
}
|
|
|
|
1;
|