Further update to new ledger-like internals

This commit is contained in:
Juerd Waalboer 2019-11-05 00:57:39 +01:00
parent 5a7a7184dd
commit 1cbc906f1e
22 changed files with 153 additions and 149 deletions

View file

@ -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;

View file

@ -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} };
}

View file

@ -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 {

View file

@ -7,23 +7,9 @@ HELP "deposit <amount>" => "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;
}

View file

@ -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)

View file

@ -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;
}

View file

@ -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;

View file

@ -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 {

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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} }

View file

@ -3,7 +3,6 @@
HELP "*<N>, x<N>, <N>x, <N>*" => "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);
}

View file

@ -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";

View file

@ -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;
}

View file

@ -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;

View file

@ -6,7 +6,7 @@ HELP "split <account>..." => "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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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.";
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;