From 49ece3b0b387e3a33e1d97186973d9fa87172458 Mon Sep 17 00:00:00 2001
From: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Date: Thu, 21 Nov 2019 13:22:39 +0000
Subject: [PATCH] Bug 24080: Add 'payout' method to Koha::Account::Line

This enhancement adds a 'payout' method to Koha::Account::Line which can
be used to 'pay out' a credit to a patron.

When such a credit is 'paid out' this method will create a corresponding
account debit line with an amount equal to the amountoutstanding on the
original credit and the two acocuntlines will be immediately applied against
each other.

Test Plan:
1) Run the included tests and verify they pass.
2) Signoff

Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
---
 Koha/Account/Line.pm                |  93 +++++++++++++++++++
 Koha/Exceptions.pm                  |   4 +
 t/db_dependent/Koha/Account/Lines.t | 138 +++++++++++++++++++++++++++-
 3 files changed, 234 insertions(+), 1 deletion(-)

diff --git a/Koha/Account/Line.pm b/Koha/Account/Line.pm
index 571c26a986..6f1e5b6e00 100644
--- a/Koha/Account/Line.pm
+++ b/Koha/Account/Line.pm
@@ -278,6 +278,99 @@ sub apply {
     return $available_credit;
 }
 
+=head3 payout
+
+  $credit_accountline->payout(
+    {
+        payout_type => $payout_type,
+        register_id => $register_id,
+        staff_id    => $staff_id,
+        interface   => 'intranet',
+        amount      => $amount
+    }
+  );
+
+Used to 'pay out' a credit to a user.
+
+Payout type may be one of any existing payment types
+
+Returns the payout debit line that is created via this transaction.
+
+=cut
+
+sub payout {
+    my ( $self, $params ) = @_;
+
+    # Make sure it is a credit we are paying out
+    unless ( $self->is_credit ) {
+        Koha::Exceptions::Account::IsNotCredit->throw(
+            error => 'Account line ' . $self->id . ' is not a credit' );
+    }
+
+    # Check for mandatory parameters
+    my @mandatory =
+      ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
+    for my $param (@mandatory) {
+        unless ( defined( $params->{$param} ) ) {
+            Koha::Exceptions::MissingParameter->throw(
+                error => "The $param parameter is mandatory" );
+        }
+    }
+
+    # Make sure there is outstanding credit to pay out
+    my $outstanding = -1 * $self->amountoutstanding;
+    my $amount =
+      $params->{amount} ? $params->{amount} : $outstanding;
+    Koha::Exceptions::Account::AmountNotPositive->throw(
+        error => 'Payout amount passed is not positive' )
+      unless ( $amount > 0 );
+    Koha::Exceptions::ParameterTooHigh->throw(
+        error => "Amount to payout ($amount) is higher than amountoutstanding ($outstanding)" )
+      unless ($outstanding >= $amount );
+
+    # Make sure we record the cash register for cash transactions
+    Koha::Exceptions::Account::RegisterRequired->throw()
+      if ( C4::Context->preference("UseCashRegisters")
+        && defined( $params->{payout_type} )
+        && ( $params->{payout_type} eq 'CASH' )
+        && !defined( $params->{cash_register} ) );
+
+    my $payout;
+    $self->_result->result_source->schema->txn_do(
+        sub {
+
+            # A 'payout' is a 'debit'
+            $payout = Koha::Account::Line->new(
+                {
+                    date              => \'NOW()',
+                    amount            => $amount,
+                    debit_type_code   => 'PAYOUT',
+                    payment_type      => $params->{payout_type},
+                    amountoutstanding => $amount,
+                    manager_id        => $params->{staff_id},
+                    borrowernumber    => $self->borrowernumber,
+                    interface         => $params->{interface},
+                    branchcode        => $params->{branch},
+                    register_id       => $params->{cash_register}
+                }
+            )->store();
+
+            my $payout_offset = Koha::Account::Offset->new(
+                {
+                    debit_id => $payout->accountlines_id,
+                    type     => 'PAYOUT',
+                    amount   => $amount
+                }
+            )->store();
+
+            $self->apply( { debits => [$payout], offset_type => 'PAYOUT' } );
+            $self->status('PAID')->store;
+        }
+    );
+
+    return $payout;
+}
+
 =head3 adjust
 
 This method allows updating a debit or credit on a patron's account
diff --git a/Koha/Exceptions.pm b/Koha/Exceptions.pm
index 72a56cc3af..cf56b220ef 100644
--- a/Koha/Exceptions.pm
+++ b/Koha/Exceptions.pm
@@ -26,6 +26,10 @@ use Exception::Class (
         isa => 'Koha::Exceptions::Exception',
         description => 'A required parameter is missing'
     },
+    'Koha::Exceptions::ParameterTooHigh' => {
+        isa => 'Koha::Exceptions::Exception',
+        description => 'A passed parameter value is too high'
+    },
     'Koha::Exceptions::NoChanges' => {
         isa => 'Koha::Exceptions::Exception',
         description => 'No changes were made',
diff --git a/t/db_dependent/Koha/Account/Lines.t b/t/db_dependent/Koha/Account/Lines.t
index d63de70938..735b91fc15 100755
--- a/t/db_dependent/Koha/Account/Lines.t
+++ b/t/db_dependent/Koha/Account/Lines.t
@@ -19,7 +19,7 @@
 
 use Modern::Perl;
 
-use Test::More tests => 9;
+use Test::More tests => 10;
 use Test::Exception;
 
 use C4::Circulation qw/AddIssue AddReturn/;
@@ -606,4 +606,140 @@ subtest "void() tests" => sub {
     $schema->storage->txn_rollback;
 };
 
+subtest "payout() tests" => sub {
+
+    plan tests => 17;
+
+    $schema->storage->txn_begin;
+
+    # Create a borrower
+    my $categorycode =
+      $builder->build( { source => 'Category' } )->{categorycode};
+    my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
+
+    my $borrower = Koha::Patron->new(
+        {
+            cardnumber => 'dariahall',
+            surname    => 'Hall',
+            firstname  => 'Daria',
+        }
+    );
+    $borrower->categorycode($categorycode);
+    $borrower->branchcode($branchcode);
+    $borrower->store;
+
+    my $staff = Koha::Patron->new(
+        {
+            cardnumber => 'bobby',
+            surname    => 'Bloggs',
+            firstname  => 'Bobby',
+        }
+    );
+    $staff->categorycode($categorycode);
+    $staff->branchcode($branchcode);
+    $staff->store;
+
+    my $account = Koha::Account->new( { patron_id => $borrower->id } );
+
+    my $debit1 = Koha::Account::Line->new(
+        {
+            borrowernumber    => $borrower->borrowernumber,
+            amount            => 10,
+            amountoutstanding => 10,
+            interface         => 'commandline',
+            debit_type_code   => 'OVERDUE'
+        }
+    )->store();
+    my $credit1 = Koha::Account::Line->new(
+        {
+            borrowernumber    => $borrower->borrowernumber,
+            amount            => -20,
+            amountoutstanding => -20,
+            interface         => 'commandline',
+            credit_type_code  => 'CREDIT'
+        }
+    )->store();
+
+    is( $account->balance(), -10, "Account balance is -10" );
+    is( $debit1->amountoutstanding,
+        10, 'Overdue fee has an amount outstanding of 10' );
+    is( $credit1->amountoutstanding,
+        -20, 'Credit has an amount outstanding of -20' );
+
+    my $pay_params = {
+        interface   => 'intranet',
+        staff_id    => $staff->borrowernumber,
+        branch      => $branchcode,
+        payout_type => 'CASH',
+        amount      => 20
+    };
+
+    throws_ok { $debit1->payout($pay_params); }
+    'Koha::Exceptions::Account::IsNotCredit',
+      '->payout() can only be used with credits';
+
+    my @required =
+      ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
+    for my $required (@required) {
+        my $params = {%$pay_params};
+        delete( $params->{$required} );
+        throws_ok {
+            $credit1->payout($params);
+        }
+        'Koha::Exceptions::MissingParameter',
+          "->payout() requires the `$required` parameter is passed";
+    }
+
+    throws_ok {
+        $credit1->payout(
+            {
+                interface   => 'intranet',
+                staff_id    => $staff->borrowernumber,
+                branch      => $branchcode,
+                payout_type => 'CASH',
+                amount      => 25
+            }
+        );
+    }
+    'Koha::Exceptions::ParameterTooHigh',
+      '->payout() cannot pay out more than the amountoutstanding';
+
+    t::lib::Mocks::mock_preference( 'UseCashRegisters', 1 );
+    throws_ok {
+        $credit1->payout(
+            {
+                interface   => 'intranet',
+                staff_id    => $staff->borrowernumber,
+                branch      => $branchcode,
+                payout_type => 'CASH',
+                amount      => 10
+            }
+        );
+    }
+    'Koha::Exceptions::Account::RegisterRequired',
+      '->payout() requires a cash_register if payout_type is `CASH`';
+
+    t::lib::Mocks::mock_preference( 'UseCashRegisters', 0 );
+    my $payout = $credit1->payout(
+        {
+            interface   => 'intranet',
+            staff_id    => $staff->borrowernumber,
+            branch      => $branchcode,
+            payout_type => 'CASH',
+            amount      => 10
+        }
+    );
+
+    is( $payout->amount(),            10, "Payout amount is 10" );
+    is( $payout->amountoutstanding(), 0,  "Payout amountoutstanding is 0" );
+    is( $account->balance(),          0,  "Account balance is 0" );
+    is( $debit1->amountoutstanding,
+        10, 'Overdue fee still has an amount outstanding of 10' );
+    is( $credit1->amountoutstanding,
+        -10, 'Credit has an new amount outstanding of -10' );
+    is( $credit1->status(), 'PAID', "Credit has a new status of PAID" );
+
+    $schema->storage->txn_rollback;
+};
+
 1;
-- 
2.24.1