Bug 32833: Fix cataloguing/value_builder/unimarc_field_121a.pl
[srvgit] / Koha / Account.pm
index a90877a..e37e646 100644 (file)
@@ -20,9 +20,8 @@ package Koha::Account;
 use Modern::Perl;
 
 use Carp;
-use Data::Dumper;
-use List::MoreUtils qw( uniq );
-use Try::Tiny;
+use Data::Dumper qw( Dumper );
+use Try::Tiny qw( catch try );
 
 use C4::Circulation qw( ReturnLostItem CanBookBeRenewed AddRenewal );
 use C4::Letters;
@@ -34,9 +33,9 @@ use Koha::Patrons;
 use Koha::Account::Lines;
 use Koha::Account::Offsets;
 use Koha::Account::DebitTypes;
-use Koha::DateUtils qw( dt_from_string );
 use Koha::Exceptions;
 use Koha::Exceptions::Account;
+use Koha::Plugins;
 
 =head1 NAME
 
@@ -64,7 +63,6 @@ Koha::Account->new( { patron_id => $borrowernumber } )->pay(
         library_id  => $branchcode,
         lines       => $lines, # Arrayref of Koha::Account::Line objects to pay
         credit_type => $type,  # credit_type_code code
-        offset_type => $offset_type,    # offset type code
         item_id     => $itemnumber,     # pass the itemnumber if this is a credit pertianing to a specific item (i.e LOST_FOUND)
     }
 );
@@ -81,232 +79,39 @@ sub pay {
     my $lines         = $params->{lines};
     my $type          = $params->{type} || 'PAYMENT';
     my $payment_type  = $params->{payment_type} || undef;
-    my $credit_type   = $params->{credit_type};
-    my $offset_type   = $params->{offset_type} || $type eq 'WRITEOFF' ? 'Writeoff' : 'Payment';
     my $cash_register = $params->{cash_register};
     my $item_id       = $params->{item_id};
 
     my $userenv = C4::Context->userenv;
 
-    $credit_type ||=
-      $type eq 'WRITEOFF'
-      ? 'WRITEOFF'
-      : 'PAYMENT';
-
-    my $patron = Koha::Patrons->find( $self->{patron_id} );
-
     my $manager_id = $userenv ? $userenv->{number} : undef;
     my $interface = $params ? ( $params->{interface} || C4::Context->interface ) : C4::Context->interface;
-    Koha::Exceptions::Account::RegisterRequired->throw()
-      if ( C4::Context->preference("UseCashRegisters")
-        && defined($payment_type)
-        && ( $payment_type eq 'CASH' )
-        && !defined($cash_register) );
-
-    my @fines_paid; # List of account lines paid on with this payment
-
-    # The outcome of any attempted item renewals as a result of fines being
-    # paid off
-    my $renew_outcomes = [];
-
-    my $balance_remaining = $amount; # Set it now so we can adjust the amount if necessary
-    $balance_remaining ||= 0;
-
-    my @account_offsets;
-
-    # We were passed a specific line to pay
-    foreach my $fine ( @$lines ) {
-        my $amount_to_pay =
-            $fine->amountoutstanding > $balance_remaining
-          ? $balance_remaining
-          : $fine->amountoutstanding;
-
-        my $old_amountoutstanding = $fine->amountoutstanding;
-        my $new_amountoutstanding = $old_amountoutstanding - $amount_to_pay;
-        $fine->amountoutstanding($new_amountoutstanding)->store();
-        $balance_remaining = $balance_remaining - $amount_to_pay;
-
-        # Attempt to renew the item associated with this debit if
-        # appropriate
-        if ($fine->is_renewable) {
-            # We're ignoring the definition of $interface above, by all
-            # accounts we can't rely on C4::Context::interface, so here
-            # we're only using what we've been explicitly passed
-            my $outcome = $fine->renew_item({ interface => $interface });
-            push @{$renew_outcomes}, $outcome if $outcome;
-        }
-
-        # Same logic exists in Koha::Account::Line::apply
-        if ( C4::Context->preference('MarkLostItemsAsReturned') =~ m|onpayment|
-            && $fine->debit_type_code
-            && $fine->debit_type_code eq 'LOST'
-            && $new_amountoutstanding == 0
-            && $fine->itemnumber
-            && !(  $credit_type eq 'LOST_FOUND'
-                && $item_id == $fine->itemnumber ) )
-        {
-            C4::Circulation::ReturnLostItem( $self->{patron_id},
-                $fine->itemnumber );
-        }
-
-        my $account_offset = Koha::Account::Offset->new(
-            {
-                debit_id => $fine->id,
-                type     => $offset_type,
-                amount   => $amount_to_pay * -1,
-            }
-        );
-        push( @account_offsets, $account_offset );
-
-        if ( C4::Context->preference("FinesLog") ) {
-            logaction(
-                "FINES", 'MODIFY',
-                $self->{patron_id},
-                Dumper(
-                    {
-                        action                => 'fee_payment',
-                        borrowernumber        => $fine->borrowernumber,
-                        old_amountoutstanding => $old_amountoutstanding,
-                        new_amountoutstanding => 0,
-                        amount_paid           => $old_amountoutstanding,
-                        accountlines_id       => $fine->id,
-                        manager_id            => $manager_id,
-                        note                  => $note,
-                    }
-                ),
-                $interface
-            );
-            push( @fines_paid, $fine->id );
-        }
-    }
-
-    # Were not passed a specific line to pay, or the payment was for more
-    # than the what was owed on the given line. In that case pay down other
-    # lines with remaining balance.
-    my @outstanding_fines;
-    @outstanding_fines = $self->lines->search(
-        {
-            amountoutstanding => { '>' => 0 },
-        }
-    ) if $balance_remaining > 0;
-
-    foreach my $fine (@outstanding_fines) {
-        my $amount_to_pay =
-            $fine->amountoutstanding > $balance_remaining
-          ? $balance_remaining
-          : $fine->amountoutstanding;
-
-        my $old_amountoutstanding = $fine->amountoutstanding;
-        $fine->amountoutstanding( $old_amountoutstanding - $amount_to_pay );
-        $fine->store();
-
-        # If we need to make a note of the item associated with this line,
-        # in order that we can potentially renew it, do so.
-        my $amt = $old_amountoutstanding - $amount_to_pay;
-        if ( $fine->is_renewable ) {
-            my $outcome = $fine->renew_item({ interface => $interface });
-            push @{$renew_outcomes}, $outcome if $outcome;
-        }
-
-        if ( C4::Context->preference('MarkLostItemsAsReturned') =~ m|onpayment|
-            && $fine->debit_type_code
-            && $fine->debit_type_code eq 'LOST'
-            && $fine->amountoutstanding == 0
-            && $fine->itemnumber
-            && !(  $credit_type eq 'LOST_FOUND'
-                && $item_id == $fine->itemnumber ) )
-        {
-            C4::Circulation::ReturnLostItem( $self->{patron_id},
-                $fine->itemnumber );
-        }
-
-        my $account_offset = Koha::Account::Offset->new(
-            {
-                debit_id => $fine->id,
-                type     => $offset_type,
-                amount   => $amount_to_pay * -1,
-            }
-        );
-        push( @account_offsets, $account_offset );
-
-        if ( C4::Context->preference("FinesLog") ) {
-            logaction(
-                "FINES", 'MODIFY',
-                $self->{patron_id},
-                Dumper(
-                    {
-                        action                => "fee_$type",
-                        borrowernumber        => $fine->borrowernumber,
-                        old_amountoutstanding => $old_amountoutstanding,
-                        new_amountoutstanding => $fine->amountoutstanding,
-                        amount_paid           => $amount_to_pay,
-                        accountlines_id       => $fine->id,
-                        manager_id            => $manager_id,
-                        note                  => $note,
-                    }
-                ),
-                $interface
-            );
-            push( @fines_paid, $fine->id );
-        }
-
-        $balance_remaining = $balance_remaining - $amount_to_pay;
-        last unless $balance_remaining > 0;
-    }
-
-    $description ||= $type eq 'WRITEOFF' ? 'Writeoff' : q{};
-
-    my $payment = Koha::Account::Line->new(
-        {
-            borrowernumber    => $self->{patron_id},
-            date              => dt_from_string(),
-            amount            => 0 - $amount,
-            description       => $description,
-            credit_type_code  => $credit_type,
-            payment_type      => $payment_type,
-            amountoutstanding => 0 - $balance_remaining,
-            manager_id        => $manager_id,
-            interface         => $interface,
-            branchcode        => $library_id,
-            register_id       => $cash_register,
-            note              => $note,
-            itemnumber        => $item_id,
-        }
-    )->store();
-
-    foreach my $o ( @account_offsets ) {
-        $o->credit_id( $payment->id() );
-        $o->store();
-    }
-
-    C4::Stats::UpdateStats(
+    my $payment = $self->payin_amount(
         {
-            branch         => $library_id,
-            type           => lc($type),
-            amount         => $amount,
-            borrowernumber => $self->{patron_id},
+            interface     => $interface,
+            type          => $type,
+            amount        => $amount,
+            payment_type  => $payment_type,
+            cash_register => $cash_register,
+            user_id       => $manager_id,
+            library_id    => $library_id,
+            item_id       => $item_id,
+            description   => $description,
+            note          => $note,
+            debits        => $lines
         }
     );
 
-    if ( C4::Context->preference("FinesLog") ) {
-        logaction(
-            "FINES", 'CREATE',
-            $self->{patron_id},
-            Dumper(
-                {
-                    action            => "create_$type",
-                    borrowernumber    => $self->{patron_id},
-                    amount            => 0 - $amount,
-                    amountoutstanding => 0 - $balance_remaining,
-                    credit_type_code  => $credit_type,
-                    accountlines_paid => \@fines_paid,
-                    manager_id        => $manager_id,
-                }
-            ),
-            $interface
-        );
+    # NOTE: Pay historically always applied as much credit as it could to all
+    # existing outstanding debits, whether passed specific debits or otherwise.
+    if ( $payment->amountoutstanding ) {
+        $payment =
+          $payment->apply(
+            { debits => [ $self->outstanding_debits->as_list ] } );
     }
 
+    my $patron = Koha::Patrons->find( $self->{patron_id} );
+    my @account_offsets = $payment->credit_offsets({ type => 'APPLY' })->as_list;
     if ( C4::Context->preference('UseEmailReceipts') ) {
         if (
             my $letter = C4::Letters::GetPreparedLetter(
@@ -335,6 +140,11 @@ sub pay {
         }
     }
 
+    my $renew_outcomes = [];
+    for my $message ( @{$payment->object_messages} ) {
+        push @{$renew_outcomes}, $message->payload;
+    }
+
     return { payment_id => $payment->id, renew_result => $renew_outcomes };
 }
 
@@ -364,6 +174,7 @@ $credit_type can be any of:
   - 'OVERPAYMENT'
   - 'PAYMENT'
   - 'WRITEOFF'
+  - 'PROCESSING_FOUND'
 
 =cut
 
@@ -400,7 +211,7 @@ sub add_credit {
     Koha::Exceptions::Account::RegisterRequired->throw()
       if ( C4::Context->preference("UseCashRegisters")
         && defined($payment_type)
-        && ( $payment_type eq 'CASH' )
+        && ( $payment_type eq 'CASH' || $payment_type eq 'SIP00' )
         && !defined($cash_register) );
 
     my $line;
@@ -432,8 +243,8 @@ sub add_credit {
                 my $account_offset = Koha::Account::Offset->new(
                     {
                         credit_id => $line->id,
-                        type   => $Koha::Account::offset_type->{$credit_type} // $Koha::Account::offset_type->{CREDIT},
-                        amount => $amount
+                        type      => 'CREATE',
+                        amount    => $amount * -1
                     }
                 )->store();
 
@@ -446,6 +257,17 @@ sub add_credit {
                     }
                 ) if grep { $credit_type eq $_ } ( 'PAYMENT', 'WRITEOFF' );
 
+                Koha::Plugins->call(
+                    'after_account_action',
+                    {
+                        action  => "add_credit",
+                        payload => {
+                            type => lc($credit_type),
+                            line => $line->get_from_storage, #TODO Seems unneeded
+                        }
+                    }
+                );
+
                 if ( C4::Context->preference("FinesLog") ) {
                     logaction(
                         "FINES", 'CREATE',
@@ -485,23 +307,112 @@ sub add_credit {
     return $line;
 }
 
+=head3 payin_amount
+
+    my $credit = $account->payin_amount(
+        {
+            amount          => $amount,
+            type            => $credit_type,
+            payment_type    => $payment_type,
+            cash_register   => $register_id,
+            interface       => $interface,
+            library_id      => $branchcode,
+            user_id         => $staff_id,
+            debits          => $debit_lines,
+            description     => $description,
+            note            => $note
+        }
+    );
+
+This method allows an amount to be paid into a patrons account and immediately applied against debts.
+
+You can optionally pass a debts parameter which consists of an arrayref of Koha::Account::Line debit lines.
+
+$credit_type can be any of:
+  - 'PAYMENT'
+  - 'WRITEOFF'
+  - 'FORGIVEN'
+
+=cut
+
+sub payin_amount {
+    my ( $self, $params ) = @_;
+
+    # check for mandatory params
+    my @mandatory = ( 'interface', 'amount', 'type' );
+    for my $param (@mandatory) {
+        unless ( defined( $params->{$param} ) ) {
+            Koha::Exceptions::MissingParameter->throw(
+                error => "The $param parameter is mandatory" );
+        }
+    }
+
+    # Check for mandatory register
+    Koha::Exceptions::Account::RegisterRequired->throw()
+      if ( C4::Context->preference("UseCashRegisters")
+        && defined( $params->{payment_type} )
+        && ( $params->{payment_type} eq 'CASH' || $params->{payment_type} eq 'SIP00' )
+        && !defined($params->{cash_register}) );
+
+    # amount should always be passed as a positive value
+    my $amount = $params->{amount};
+    unless ( $amount > 0 ) {
+        Koha::Exceptions::Account::AmountNotPositive->throw(
+            error => 'Payin amount passed is not positive' );
+    }
+
+    my $credit;
+    my $schema = Koha::Database->new->schema;
+    $schema->txn_do(
+        sub {
+
+            # Add payin credit
+            $credit = $self->add_credit($params);
+
+            # Offset debts passed first
+            if ( exists( $params->{debits} ) ) {
+                $credit = $credit->apply(
+                    {
+                        debits => $params->{debits}
+                    }
+                );
+            }
+
+            # Offset against remaining balance if AutoReconcile
+            if ( C4::Context->preference("AccountAutoReconcile")
+                && $credit->amountoutstanding != 0 )
+            {
+                $credit = $credit->apply(
+                    {
+                        debits => [ $self->outstanding_debits->as_list ]
+                    }
+                );
+            }
+        }
+    );
+
+    return $credit;
+}
+
 =head3 add_debit
 
 This method allows adding debits to a patron's account
 
-my $debit_line = Koha::Account->new({ patron_id => $patron_id })->add_debit(
-    {
-        amount       => $amount,
-        description  => $description,
-        note         => $note,
-        user_id      => $user_id,
-        interface    => $interface,
-        library_id   => $library_id,
-        type         => $debit_type,
-        item_id      => $item_id,
-        issue_id     => $issue_id
-    }
-);
+    my $debit_line = Koha::Account->new({ patron_id => $patron_id })->add_debit(
+        {
+            amount           => $amount,
+            description      => $description,
+            note             => $note,
+            user_id          => $user_id,
+            interface        => $interface,
+            library_id       => $library_id,
+            type             => $debit_type,
+            transaction_type => $transaction_type,
+            cash_register    => $register_id,
+            item_id          => $item_id,
+            issue_id         => $issue_id
+        }
+    );
 
 $debit_type can be any of:
   - ACCOUNT
@@ -517,6 +428,7 @@ $debit_type can be any of:
   - RENT_RENEW
   - RENT_DAILY_RENEW
   - RESERVE
+  - PAYOUT
 
 =cut
 
@@ -533,6 +445,13 @@ sub add_debit {
         }
     }
 
+    # check for cash register if using cash
+    Koha::Exceptions::Account::RegisterRequired->throw()
+      if ( C4::Context->preference("UseCashRegisters")
+        && defined( $params->{transaction_type} )
+        && ( $params->{transaction_type} eq 'CASH' || $params->{payment_type} eq 'SIP00' )
+        && !defined( $params->{cash_register} ) );
+
     # amount should always be a positive value
     my $amount = $params->{amount};
     unless ( $amount > 0 ) {
@@ -540,15 +459,16 @@ sub add_debit {
             error => 'Debit amount passed is not positive' );
     }
 
-    my $description = $params->{description} // q{};
-    my $note        = $params->{note} // q{};
-    my $user_id     = $params->{user_id};
-    my $interface   = $params->{interface};
-    my $library_id  = $params->{library_id};
-    my $debit_type  = $params->{type};
-    my $item_id     = $params->{item_id};
-    my $issue_id    = $params->{issue_id};
-    my $offset_type = $Koha::Account::offset_type->{$debit_type} // 'Manual Debit';
+    my $description      = $params->{description} // q{};
+    my $note             = $params->{note} // q{};
+    my $user_id          = $params->{user_id};
+    my $interface        = $params->{interface};
+    my $library_id       = $params->{library_id};
+    my $cash_register    = $params->{cash_register};
+    my $debit_type       = $params->{type};
+    my $transaction_type = $params->{transaction_type};
+    my $item_id          = $params->{item_id};
+    my $issue_id         = $params->{issue_id};
 
     my $line;
     my $schema = Koha::Database->new->schema;
@@ -565,13 +485,14 @@ sub add_debit {
                         description       => $description,
                         debit_type_code   => $debit_type,
                         amountoutstanding => $amount,
-                        payment_type      => undef,
+                        payment_type      => $transaction_type,
                         note              => $note,
                         manager_id        => $user_id,
                         interface         => $interface,
                         itemnumber        => $item_id,
                         issue_id          => $issue_id,
                         branchcode        => $library_id,
+                        register_id       => $cash_register,
                         (
                             $debit_type eq 'OVERDUE'
                             ? ( status => 'UNRETURNED' )
@@ -584,7 +505,7 @@ sub add_debit {
                 my $account_offset = Koha::Account::Offset->new(
                     {
                         debit_id => $line->id,
-                        type     => $offset_type,
+                        type     => 'CREATE',
                         amount   => $amount
                     }
                 )->store();
@@ -662,14 +583,14 @@ sub payout_amount {
     # Check for mandatory register
     Koha::Exceptions::Account::RegisterRequired->throw()
       if ( C4::Context->preference("UseCashRegisters")
-        && ( $params->{payout_type} eq 'CASH' )
+        && ( $params->{payout_type} eq 'CASH' || $params->{payout_type} eq 'SIP00' )
         && !defined($params->{cash_register}) );
 
     # Amount should always be passed as a positive value
     my $amount = $params->{amount};
     unless ( $amount > 0 ) {
         Koha::Exceptions::Account::AmountNotPositive->throw(
-            error => 'Debit amount passed is not positive' );
+            error => 'Payout amount passed is not positive' );
     }
 
     # Amount should always be less than or equal to outstanding credit
@@ -696,20 +617,20 @@ sub payout_amount {
                 {
                     amount            => $params->{amount},
                     type              => 'PAYOUT',
-                    payment_type      => $params->{payout_type},
+                    transaction_type  => $params->{payout_type},
                     amountoutstanding => $params->{amount},
-                    manager_id        => $params->{staff_id},
+                    user_id           => $params->{staff_id},
                     interface         => $params->{interface},
                     branchcode        => $params->{branch},
-                    register_id       => $params->{cash_register}
+                    cash_register     => $params->{cash_register}
                 }
             );
 
             # Offset against credits
             for my $credit ( @{$outstanding_credits} ) {
-                $credit->apply(
-                    { debits => [$payout], offset_type => 'PAYOUT' } );
+                $credit->apply( { debits => [$payout] } );
                 $payout->discard_changes;
+                last if $payout->amountoutstanding == 0;
             }
 
             # Set payout as paid
@@ -739,8 +660,7 @@ my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_debits;
 
 It returns the debit lines with outstanding amounts for the patron.
 
-In scalar context, it returns a Koha::Account::Lines iterator. In list context, it will
-return a list of Koha::Account::Line objects.
+It returns a Koha::Account::Lines iterator.
 
 =cut
 
@@ -761,8 +681,7 @@ my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_credits
 
 It returns the credit lines with outstanding amounts for the patron.
 
-In scalar context, it returns a Koha::Account::Lines iterator. In list context, it will
-return a list of Koha::Account::Line objects.
+It returns a Koha::Account::Lines iterator.
 
 =cut
 
@@ -861,32 +780,6 @@ sub reconcile_balance {
 
 1;
 
-=head2 Name mappings
-
-=head3 $offset_type
-
-=cut
-
-our $offset_type = {
-    'CREDIT'           => 'Manual Credit',
-    'FORGIVEN'         => 'Writeoff',
-    'LOST_FOUND'       => 'Lost Item Found',
-    'OVERPAYMENT'      => 'Overpayment',
-    'PAYMENT'          => 'Payment',
-    'WRITEOFF'         => 'Writeoff',
-    'ACCOUNT'          => 'Account Fee',
-    'ACCOUNT_RENEW'    => 'Account Fee',
-    'RESERVE'          => 'Reserve Fee',
-    'PROCESSING'       => 'Processing Fee',
-    'LOST'             => 'Lost Item',
-    'RENT'             => 'Rental Fee',
-    'RENT_DAILY'       => 'Rental Fee',
-    'RENT_RENEW'       => 'Rental Fee',
-    'RENT_DAILY_RENEW' => 'Rental Fee',
-    'OVERDUE'          => 'OVERDUE',
-    'RESERVE_EXPIRED'  => 'Hold Expired'
-};
-
 =head1 AUTHORS
 
 =encoding utf8