diff --git a/lib/RevBank/Cart.pm b/lib/RevBank/Cart.pm index 6f4adc0..a154e46 100644 --- a/lib/RevBank/Cart.pm +++ b/lib/RevBank/Cart.pm @@ -19,14 +19,16 @@ sub _call_old_hooks { my $data = $entry->{attributes}; - for ($entry, $entry->contras) { - my $item = { - %$data, - amount => $_->{amount}, - description => $_->{description}, - }; + for (1 .. $entry->quantity) { + for ($entry, $entry->contras) { + my $item = { + %$data, + amount => $_->{amount}, + description => $_->{description}, + }; - RevBank::Plugins::call_hooks($hook, $self, $_->{user}, $item); + RevBank::Plugins::call_hooks($hook, $self, $_->{user}, $item); + } } } @@ -118,14 +120,17 @@ sub checkout { my %deltas; for my $entry (@$entries) { $entry->user($user); - $deltas{$_->{user}} += $_->{amount} for $entry, $entry->contras; + + $deltas{$_->{user}} += $_->{amount} * $entry->quantity + for $entry, $entry->contras; } my $transaction_id = time() - 1300000000; RevBank::Plugins::call_hooks("checkout", $self, $user, $transaction_id); for my $account (keys %deltas) { - RevBank::Users::update($account, $deltas{$account}, $transaction_id); + RevBank::Users::update($account, $deltas{$account}, $transaction_id) + if $deltas{$account} != 0; } RevBank::Plugins::call_hooks("checkout_done", $self, $user, $transaction_id); @@ -142,24 +147,45 @@ sub select_items { my @matches; for my $entry (@{ $self->{entries} }) { my %attributes = %{ $entry->{attributes} }; - for my $item ($entry, $entry->contras) { - push @matches, { %attributes, %$item } - if @_ == 1 # No key or match given: match everything - or @_ == 2 and $entry->has_attribute($key) # Just a key + for (1 .. $entry->quantity) { + for my $item ($entry, $entry->contras) { + push @matches, { %attributes, %$item } + if @_ == 1 # No key or match given: match everything + or @_ == 2 and $entry->has_attribute($key) # Just a key + } } } return @matches; } +sub entries { + my ($self, $attribute) = @_; + + my @entries = @{ $self->{entries} }; + return grep $_->has_attribute($attribute), @entries if defined $attribute; + return @entries; +} + sub is_multi_user { Carp::carp("\$cart->is_multi_user is no longer supported, ignoring"); } sub changed { my ($self) = @_; - return delete $self->{changed}; + + 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 { + my ($self) = @_; + return List::Util::sum(map $_->{amount} * $_->quantity, @{ $self->{entries} }); } 1; - diff --git a/lib/RevBank/Cart/Entry.pm b/lib/RevBank/Cart/Entry.pm index 0db244a..258496e 100644 --- a/lib/RevBank/Cart/Entry.pm +++ b/lib/RevBank/Cart/Entry.pm @@ -34,6 +34,8 @@ sub add_contra { amount => $amount, # should usually have opposite sign (+/-) description => $description, }; + + $self->attribute('changed', 1); } sub has_attribute { @@ -57,12 +59,13 @@ sub quantity { if (defined $new) { $new >= 0 or croak "Quantity must be positive"; $$ref = $new; + $self->attribute('changed', 1); } return $$ref; } -sub multiple { +sub multiplied { my ($self) = @_; return $self->{quantity} != 1; @@ -81,7 +84,7 @@ sub as_printable { $self->sanity_check; my @s; - push @s, $self->{quantity} . "x {" if $self->multiple; + push @s, $self->{quantity} . "x {" if $self->multiplied; # Normally, the implied sign is "+", and an "-" is only added for negative # numbers. Here, the implied sign is "-", and a "+" is only added for @@ -102,7 +105,7 @@ sub as_printable { } - push @s, "}" if $self->multiple; + push @s, "}" if $self->multiplied; return @s; } @@ -122,10 +125,10 @@ sub as_loggable { my $description = $quantity == 1 ? $_->{description} - : sprintf("[%fx%.2f]", $quantity, $_->{amount}); + : sprintf("[%sx %.2f] %s", $quantity, abs($_->{amount}), $_->{description}); push @s, sprintf( - "%-12s %4s EUR %5.2f %s", + "%-12s %4s EUR %5.2f # %s", $_->{user}, ($total > 0 ? 'GAIN' : $total < 0 ? 'LOSE' : ''), abs($total), @@ -143,6 +146,7 @@ sub user { croak "User can only be set once" if defined $self->{user}; $self->{user} = $new; + $self->attribute('changed', 1); $_->{description} =~ s/\$you/$new/g for $self, @{ $self->{contras} }; } diff --git a/lib/RevBank/Messages.pm b/lib/RevBank/Messages.pm index 08be5cf..e33ab4a 100644 --- a/lib/RevBank/Messages.pm +++ b/lib/RevBank/Messages.pm @@ -24,8 +24,12 @@ sub hook_plugin_fail { sub hook_cart_changed { my ($class, $cart) = @_; $cart->size or return; + say "Pending:"; $cart->display; - say "Enter username to pay/finish or 'abort' to abort.\n"; + + my $sum = $cart->sum; + my $what = $sum > 0 ? "add %.2f" : "pay %.2f"; + say sprintf "Enter username to $what; type 'abort' to abort.\n", abs $sum; } sub hook_abort { diff --git a/plugins/deposit b/plugins/deposit index 9d83c39..ae5b247 100644 --- a/plugins/deposit +++ b/plugins/deposit @@ -7,23 +7,9 @@ HELP "deposit " => "Deposit into an account"; sub command :Tab(deposit) { my ($self, $cart, $command) = @_; - if ($command eq 'deposit') { - return "Amount to deposit into your account", \&amount; - } + $command eq 'deposit' or return NEXT; - if ($cart->select_items('is_deposit')) { - # No other plugin recognised the input, so it must be a new user. - $self->{new_user} = $command; - - my $x = RevBank::Plugin::adduser->can("command") - ? "Please use \e[4madduser\e[0m instead." - : "Please enable the \e[4madduser\e[0m plugin."; - warn "Creating accounts with \e[4mdeposit\e[m is deprecated. $x\n"; - - return "Add new account for user '$command'?", \&create; - } - - return NEXT; + return "Amount to deposit into your account", \&amount; } sub amount :Tab(13.37,42) { @@ -37,7 +23,7 @@ sub amount :Tab(13.37,42) { return $message . "How are we receiving this EUR $amount?", \&how if keys %{ $self->{deposit_methods} }; - $cart->add(undef, +$self->{amount}, "Deposit", { is_deposit => 1 }); + $cart->add(+$self->{amount}, "Deposit", { is_deposit => 1 }); return ACCEPT; } @@ -55,7 +41,7 @@ sub how :Tab(&how_tab) { return shift @{ $how->{prompts} }, \&how_prompt; } - $cart->add(undef, +$self->{amount}, $how->{description}, { is_deposit => 1, method => $how->{_key} }); + $cart->add(+$self->{amount}, $how->{description}, { is_deposit => 1, method => $how->{_key} }); return ACCEPT; } @@ -77,19 +63,6 @@ sub how_prompt { my $desc = sprintf $how->{description}, @{ $how->{answers} }; - $cart->add(undef, +$self->{amount}, $desc, { is_deposit => 1, method => $how->{_key} }); + $cart->add(+$self->{amount}, $desc, { is_deposit => 1, method => $how->{_key} }); return ACCEPT; } - -sub create :Tab(yes,no) { - my ($self, $cart, $yesno) = @_; - my $user = $self->{new_user}; - - if ($yesno eq "y" or $yesno eq "yes") { - RevBank::Users::create( $user ); - $cart->checkout( $user ); - return ACCEPT; - } - return ABORT; -} - diff --git a/plugins/deposit_iban_qr b/plugins/deposit_iban_qr index afc6c9c..241b33b 100644 --- a/plugins/deposit_iban_qr +++ b/plugins/deposit_iban_qr @@ -28,9 +28,9 @@ sub command { NEXT } sub hook_checkout { my ($class, $cart, $user, $transaction_id) = @_; - my @items = $cart->select_items("is_deposit"); + my @entries = $cart->entries("is_deposit"); - my $amount = sum map $_->{amount}, grep $_->{method} eq "iban", @items; + my $amount = sum map $_->{amount}, grep $_->attribute('method') eq 'iban', @entries; if (defined $amount && $amount > 0) { my $pid = open2 my $out, my $in, qw(qrencode -t ansiutf8 -m 2) diff --git a/plugins/dinnerbonus b/plugins/dinnerbonus index 7c2981d..1be2299 100644 --- a/plugins/dinnerbonus +++ b/plugins/dinnerbonus @@ -2,29 +2,36 @@ HELP "dinnerbonus" => "Add fee for cooking supplies"; +my $bonus = 1.00; + sub command :Tab(kookbonus,dinnerbonus) { my ($self, $cart, $command) = @_; - my $bonus = 1.00; + my @users = map $_->{user}, map $_->contras, $cart->entries('is_take'); - $command eq 'kookbonus' or $command eq 'dinnerbonus' + (@users and $command eq 'kookpotje') # common mistake promoted to feature + or $command eq 'kookbonus' + or $command eq 'dinnerbonus' or return NEXT; - my @users = grep !/^\$you$/, map $_->{user}, $cart->select_items - or return REJECT, "$command requires a pending transaction."; - - for my $user (@users) { - $cart->add( $user, -$bonus, "Kookbonus by \$you" ); - } - + @users or return REJECT, "$command requires a pending 'take'."; my $users = join '/', @users; - $cart->add( - "kookpotje", + my $target = parse_user("kookpotje") + or return ABORT, "User 'kookpotje' does not exist"; + + my $entry = $cart->add(0, "Kookbonus"); + + $entry->add_contra( + $target, scalar @users * $bonus, "Kookbonus from $users by \$you" ); + for my $user (@users) { + $entry->add_contra( $user, -$bonus, "Kookbonus by \$you" ); + } + return ACCEPT; } diff --git a/plugins/give b/plugins/give index 66a0503..db39a58 100644 --- a/plugins/give +++ b/plugins/give @@ -41,8 +41,10 @@ sub reason :Tab(whatevah) { my $user = parse_user($input); my $reason = $user ? "" : " ($input)"; - $cart->add(undef, -$amount, "Given to $benificiary" . $reason); - $cart->add($benificiary, +$amount, "Received from \$you" . $reason); + $cart + ->add(-$amount, "Given to $benificiary" . $reason) + ->add_contra($benificiary, +$amount, "Received from \$you" . $reason); + $cart->checkout($user) if $user; return ACCEPT; diff --git a/plugins/log b/plugins/log index cdc166d..d262393 100644 --- a/plugins/log +++ b/plugins/log @@ -49,7 +49,7 @@ sub hook_user_balance { sub hook_checkout { my ($class, $cart, $username, $transaction_id) = @_; - _log("CHECKOUT $transaction_id $_") for $cart->as_strings; + _log("CHECKOUT $transaction_id $_") for map $_->as_loggable, $cart->entries; } sub hook_register { diff --git a/plugins/market b/plugins/market index 07b0073..0078a98 100644 --- a/plugins/market +++ b/plugins/market @@ -37,8 +37,9 @@ sub command :Tab(market,&tab) { my $space = parse_amount($product->{ space }) or return NEXT; my $description = $product->{description}; - $cart->add(undef, -($seller + $space), $description,{product_id=>$command}); - $cart->add($username, 0+$seller, "\$you bought $description") + $cart + ->add(-($seller + $space), "$description (sold by $username)", {product_id=>$command}) + ->add_contra($username, 0+$seller, "\$you bought $description") if 0+$seller; return ACCEPT; } diff --git a/plugins/pfand b/plugins/pfand index bbe4dc2..0aacfad 100644 --- a/plugins/pfand +++ b/plugins/pfand @@ -29,7 +29,7 @@ sub product :Tab(&tab) { my $pfand = _read_pfand->{ $product }; if ($pfand) { - $cart->add(undef, +$pfand, "Pfand zurueck", { is_return => 1 }); + $cart->add(+$pfand, "Pfand zurueck", { is_return => 1 }); } else { say "$product: Kein Pfand"; } @@ -40,14 +40,15 @@ sub tab { return keys %{ _read_pfand() }; } -sub hook_add { - my ($class, $cart, $user, $item) = @_; - return if defined $user; - return if exists $item->{is_return}; - return if not exists $item->{product_id}; +sub hook_add_entry { + my ($class, $cart, $entry) = @_; + return if $entry->has_attribute('is_return'); + return if not $entry->has_attribute('product_id'); - my $pfand = _read_pfand->{ $item->{product_id} } or return; + my $pfand = _read_pfand->{ $entry->attribute('product_id') } or return; - $cart->add(undef, -$pfand, "Pfand", { is_pfand => 1 }); + $cart->add(-$pfand, "Pfand", { is_pfand => 1 }); + + return; } diff --git a/plugins/products b/plugins/products index 257fffb..ba8c10c 100644 --- a/plugins/products +++ b/plugins/products @@ -35,7 +35,6 @@ sub command :Tab(edit,&tab) { my $price = parse_amount( $product->{price} ) or return NEXT; $cart->add( - undef, -$price, $product->{description}, { product_id => $product->{id} } diff --git a/plugins/repeat b/plugins/repeat index f090bd1..fb00cd7 100644 --- a/plugins/repeat +++ b/plugins/repeat @@ -3,7 +3,6 @@ HELP "*, x, x, *" => "Repeat previous/next product N times"; my $err_stacked = "Stacked repetition is not supported."; -my $err_multi = "Repetition not supported in multi-user transactions."; my $err_pfand = "Plugins 'pfand' and 'repeat' cannot be combined."; my $limit = 24; @@ -22,23 +21,21 @@ sub _do_repeat { sub command { my ($self, $cart, $command) = @_; - my @items = $cart->select_items; - my $last = $items[-1]; - - return ABORT, $err_pfand if grep $_->{is_pfand}, @items; + return ABORT, $err_pfand if $cart->entries('is_pfand'); my ($pre, $post) = $command =~ /^(\d+)?[x*](\d+)?$/ or return NEXT; + my $last = ($cart->entries)[-1]; + return NEXT if $pre and $post; # 123x123 -> invalid syntax if ($post) { - return REJECT, $err_multi if $cart->is_multi_user; return REJECT, $err_limit if $post > $limit; return ABORT, "Can't repeat an empty transaction." if not $cart->size; - return REJECT, $err_stacked if $last->{_repeated}; + return REJECT, $err_stacked if $last->multiplied; - _do_repeat($cart, $last, $post); + $last->quantity($post); return ACCEPT; } @@ -47,10 +44,10 @@ sub command { if (not $pre and not $post) { # Lone operator. Convert withdrawal into repetition. - if ($last->{is_withdrawal}) { + if ($last->has_attribute('is_withdrawal')) { $pre = abs $last->{amount}; $pre == int $pre or return REJECT, "Repeat only works on integers."; - $cart->delete($last->{user}, -1); + $cart->delete($last); $item_replaced = 1; } elsif (not $cart->size) { return ABORT, "Can't repeat an empty transaction."; @@ -61,11 +58,11 @@ sub command { $pre = abs $pre; # withdrawal is negative return REJECT, $err_limit if $pre > $limit; - $cart->add(undef, 0, "Next product repeated $pre times", { _repeat => abs $pre }); + $cart->add(0, "Next product repeated $pre times", { _repeat => abs $pre }); return ACCEPT; } - return REJECT, $err_stacked if $last->{_repeated}; + return REJECT, $err_stacked if $last->multiplied; return "Multiply previous product by", \&repeat; } @@ -77,39 +74,24 @@ sub repeat { return REJECT, $err_limit if $arg > $limit; - my @items = $cart->select_items; - my $last = $items[-1]; - - _do_repeat($cart, $last, $arg); + ($cart->entries)[-1]->quantity($arg); return ACCEPT; } -sub hook_added { - my ($self, $cart, $user, $item) = @_; +sub hook_added_entry { + my ($self, $cart, $entry) = @_; $cart->size >= 2 or return; - my @items = $cart->select_items; - my @planned = $cart->select_items('_repeat'); - my @repeated = $cart->select_items('_repeated'); + my @entries = $cart->entries; + my @planned = $cart->entries('_repeat'); + my @repeated = grep $_->multiplied, $cart->entries; - return ABORT, $err_multi if $cart->is_multi_user and @planned || @repeated; return ABORT, "Multiple repeats queued; I'm confused." if @planned > 1; return if not @planned; - return ABORT, $err_pfand if grep $_->{is_pfand}, @items; + return ABORT, $err_pfand if $cart->entries('is_pfand'); - for my $i (0 .. $#items - 1) { - my $item = $items[$i]; - $item->{_repeat} or next; + my $num = $planned[0]->attribute('_repeat'); - my $next = $items[$i + 1]; - - return ABORT, $err_stacked if $next->{_repeat}; - - my $num = $item->{_repeat}; - $cart->delete($item->{user}, $i); - - _do_repeat($cart, $next, $num); - - return; - } + $cart->delete($planned[0]); + $entries[-1]->quantity($num); } diff --git a/plugins/revspace_barcode b/plugins/revspace_barcode index 108b4a9..2464205 100644 --- a/plugins/revspace_barcode +++ b/plugins/revspace_barcode @@ -19,7 +19,6 @@ sub data { my ($self, $cart, $input) = @_; $cart->add( - undef, -0.07, "Barcode <$input>", { is_barcode => 1, barcode_data => $input } @@ -32,15 +31,15 @@ sub hook_checkout { my ($class, $cart, $username, $transaction_id) = @_; my @barcodes; - for my $item ($cart->select_items('is_barcode')) { - push @barcodes, $item->{barcode_data}; + for my $entry ($cart->entries('is_barcode')) { + push @barcodes, ($entry->attribute('barcode_data')) x $entry->quantity; } if (@barcodes) { print "\nCheck the following:\n 1. label tape is 12 mm\n 2. printer is on\n 3. wifi is enabled and connected\n\nPress enter to continue."; readline STDIN; - + my $printjob = ""; - + open my $bcgen, "-|", "/home/bar/revlabel/barcode.pl", @barcodes or warn "Could not open script 1"; diff --git a/plugins/revspace_bounties b/plugins/revspace_bounties index 89496cc..94bc3e2 100644 --- a/plugins/revspace_bounties +++ b/plugins/revspace_bounties @@ -11,7 +11,7 @@ sub command :Tab(BOUNTY1,BOUNTY2,BOUNTY3,BOUNTY4) { my ($self, $cart, $command) = @_; if ($command =~ /BOUNTY(\d+)/) { - $cart->add(undef, +$bounties{$1}[0], $bounties{$1}[1]); + $cart->add(+$bounties{$1}[0], $bounties{$1}[1]); return ACCEPT; } diff --git a/plugins/revspace_mqtt b/plugins/revspace_mqtt index 15adfea..755b703 100644 --- a/plugins/revspace_mqtt +++ b/plugins/revspace_mqtt @@ -7,7 +7,7 @@ sub command { NEXT } sub hook_checkout { my ($class, $cart, $user, $transaction_id) = @_; my $filename = "revbank.sales"; - my @items = $cart->select_items('product_id') or return; + my @entries = $cart->entries('product_id') or return; my %already_retained; my %stats = do { @@ -17,9 +17,9 @@ sub hook_checkout { : () }; - $stats{ $_->{product_id} }++ for @items; + $stats{ $_->attribute('product_id') } += $_->quantity for @entries; - for (@items) { + for (@entries) { my $product = $_->{product_id}; publish "revspace/bank/sale" => $product; diff --git a/plugins/split b/plugins/split index 336c901..036e1c2 100644 --- a/plugins/split +++ b/plugins/split @@ -6,7 +6,7 @@ HELP "split ..." => "Split the bill with others"; sub _select_split { my ($cart) = @_; - grep $_->{amount} < 0, grep $_->{user} eq '$you', $cart->select_items + grep $_->{amount} < 0, $cart->entries } sub command :Tab(take,steal,split) { @@ -36,12 +36,12 @@ sub arg :Tab(USERS) { my $total = sprintf "%.2f", @$users * $each; my $desc = join " + ", map $_->{description}, _select_split($cart); - for my $user (@$users) { - $cart->add( $user, -$each, "Taken by \$you (Split: $desc)" ); - } - my $users = join '/', @$users; - $cart->add( undef, $total, "Taken from $users (Split: $desc)" ); + my $entry = $cart->add($total, "Taken from $users (Split: $desc)" ); + + for my $user (@$users) { + $entry->add_contra( $user, -$each, "Taken by \$you (Split: $desc)" ); + } return ACCEPT; } diff --git a/plugins/stock b/plugins/stock index dc4217f..8e5a9c1 100644 --- a/plugins/stock +++ b/plugins/stock @@ -21,7 +21,7 @@ sub hook_checkout { ? "revbank.voorraad" : "revbank.stock"; - my @items = $cart->select_items('product_id') or return; + my @entries = $cart->entries('product_id') or return; my %stock = do { my $in; @@ -30,7 +30,7 @@ sub hook_checkout { : () }; - $stock{ $_->{product_id} }-- for @items; + $stock{ $_->attribute('product_id') } -= $_->quantity for @entries; open my $out, '>', "$filename.$$" or warn "$filename.$$: $!"; printf {$out} "%-16s %+9d\n", $_, $stock{$_} for sort keys %stock; diff --git a/plugins/take b/plugins/take index 1b18f93..e750c87 100644 --- a/plugins/take +++ b/plugins/take @@ -59,12 +59,11 @@ sub reason :Tab(bbq,NOABORT) { # finish my $each = $self->{each}; my $total = $self->{total}; - for my $user (@users) { - $cart->add( $user, -$each, "Taken by \$you ($reason)" ); - } - my $users = join '/', @users; - $cart->add( undef, $total, "Taken from $users ($reason)" ); + my $entry = $cart->add($total, "Taken from $users ($reason)", { is_take => 1 }); + for my $user (@users) { + $entry->add_contra( $user, -$each, "Taken by \$you ($reason)" ); + } return ACCEPT; } diff --git a/plugins/undo b/plugins/undo index d3ddfab..ea42627 100644 --- a/plugins/undo +++ b/plugins/undo @@ -19,10 +19,18 @@ sub undo { open my $in, '<', $filename or die "$filename: $!"; open my $out, '>', "$filename.$$" or die "$filename.$$: $!"; + my $description = "Undo $tid"; + + my $entry; + while (defined(my $line = readline $in)) { if ($line =~ /^\Q$tid\E\s/) { my (undef, $user, $delta) = split " ", $line; - $cart->add($user, $delta, "Undo $tid"); + + $entry ||= $cart->add(0, $description); + $entry->{FORCE} = 1; + + $entry->add_contra($user, $delta, "Undo $tid"); } else { print {$out} $line; } @@ -31,7 +39,7 @@ sub undo { close $out or die $!; if ($cart->size) { rename "$filename.$$", $filename or die $!; - $cart->checkout(); + $cart->checkout('**UNDO**'); } else { return ABORT, "Transaction ID '$tid' not found in undo log."; } diff --git a/plugins/unlisted b/plugins/unlisted index 1ef9964..62ea0ef 100644 --- a/plugins/unlisted +++ b/plugins/unlisted @@ -15,7 +15,7 @@ sub amount { $self->{amount} = parse_amount($arg) or return REJECT, "Invalid amount."; if ($self->{command} eq 'donate') { - $cart->add(undef, -$self->{amount}, "Donation (THANK YOU!)"); + $cart->add(-$self->{amount}, "Donation (THANK YOU!)"); return ACCEPT; } @@ -24,7 +24,7 @@ sub amount { sub description { my ($self, $cart, $desc) = @_; - $cart->add(undef, -$self->{amount}, $desc); + $cart->add(-$self->{amount}, $desc); return ACCEPT; } diff --git a/plugins/warnings b/plugins/warnings index 953c9f7..1ac1cc4 100644 --- a/plugins/warnings +++ b/plugins/warnings @@ -31,13 +31,12 @@ sub _read_warnings { sub command { NEXT } -sub hook_add { - my ($class, $cart, $user, $item) = @_; - return if defined $user; # skip market items - return if not exists $item->{product_id}; # skip unlisted, deposit, give, take +sub hook_add_entry { + my ($class, $cart, $entry) = @_; + return if not $entry->has_attribute('product_id'); # skip unlisted, deposit, give, take my @warnings = map { - $_->( $item->{product_id}, $item->{description} ) + $_->( $entry->attribute('product_id'), $entry->{description} ) } _read_warnings; return if not @warnings; diff --git a/plugins/withdraw b/plugins/withdraw index c926259..489ca07 100644 --- a/plugins/withdraw +++ b/plugins/withdraw @@ -8,7 +8,7 @@ sub command { my $amount = parse_amount($command); defined $amount or return NEXT; - $cart->add(undef, -$amount, "Withdrawal or unlisted product", + $cart->add(-$amount, "Withdrawal or unlisted product", { is_withdrawal => 1 }); return ACCEPT;