Bug 22435: Fix _set_found_trigger
[koha-ffzg.git] / Koha / Account / Line.pm
index 9f6723f..a05ab65 100644 (file)
@@ -17,17 +17,16 @@ package Koha::Account::Line;
 
 use Modern::Perl;
 
-use Carp;
-use Data::Dumper;
+use Data::Dumper qw( Dumper );
 
-use C4::Log qw(logaction);
-use C4::Overdues qw(GetFine);
+use C4::Log qw( logaction );
+use C4::Overdues qw( UpdateFine );
 
 use Koha::Account::CreditType;
 use Koha::Account::DebitType;
 use Koha::Account::Offsets;
 use Koha::Database;
-use Koha::DateUtils;
+use Koha::DateUtils qw( dt_from_string );
 use Koha::Exceptions::Account;
 use Koha::Items;
 
@@ -132,8 +131,8 @@ Return the credit_offsets linked to this account line if some exist
 =cut
 
 sub credit_offsets {
-    my ( $self ) = @_;
-    my $rs = $self->_result->account_offsets_credits;
+    my ( $self, $cond, $attr ) = @_;
+    my $rs = $self->_result->search_related( 'account_offsets_credits', $cond, $attr);
     return unless $rs;
     return Koha::Account::Offsets->_new_from_dbic($rs);
 }
@@ -145,8 +144,8 @@ Return the debit_offsets linked to this account line if some exist
 =cut
 
 sub debit_offsets {
-    my ( $self ) = @_;
-    my $rs = $self->_result->account_offsets_debits;
+    my ( $self, $cond, $attr ) = @_;
+    my $rs = $self->_result->search_related( 'account_offsets_debits', $cond, $attr);
     return unless $rs;
     return Koha::Account::Offsets->_new_from_dbic($rs);
 }
@@ -210,7 +209,10 @@ sub debits {
 
 =head3 void
 
-  $payment_accountline->void();
+  $payment_accountline->void({
+      interface => $interface,
+      [ staff_id => $staff_id, branch => $branchcode ]
+  });
 
 Used to 'void' (or reverse) a payment/credit. It will roll back any offsets
 created by the application of this credit upon any debits and mark the credit
@@ -219,17 +221,81 @@ as 'void' by updating it's status to "VOID".
 =cut
 
 sub void {
-    my ($self) = @_;
+    my ($self, $params) = @_;
+
+    # Make sure it is a credit we are voiding
+    unless ( $self->is_credit ) {
+        Koha::Exceptions::Account::IsNotCredit->throw(
+            error => 'Account line ' . $self->id . 'is not a credit' );
+    }
+
+    # Make sure it is not already voided
+    if ( $self->status && $self->status eq 'VOID' ) {
+        Koha::Exceptions::Account->throw(
+            error => 'Account line ' . $self->id . 'is already void' );
+    }
 
-    # Make sure it is a payment we are voiding
-    return unless $self->amount < 0;
+    # Check for mandatory parameters
+    my @mandatory = ( 'interface' );
+    for my $param (@mandatory) {
+        unless ( defined( $params->{$param} ) ) {
+            Koha::Exceptions::MissingParameter->throw(
+                error => "The $param parameter is mandatory" );
+        }
+    }
+
+    # More mandatory parameters
+    if ( $params->{interface} eq 'intranet' ) {
+        my @optional = ( 'staff_id', 'branch' );
+        for my $param (@optional) {
+            unless ( defined( $params->{$param} ) ) {
+                Koha::Exceptions::MissingParameter->throw( error =>
+"The $param parameter is mandatory when interface is set to 'intranet'"
+                );
+            }
+        }
+    }
 
+    # Find any applied offsets for the credit so we may reverse them
     my @account_offsets =
       Koha::Account::Offsets->search(
         { credit_id => $self->id, amount => { '<' => 0 }  } );
 
+    my $void;
     $self->_result->result_source->schema->txn_do(
         sub {
+
+            # A 'void' is a 'debit'
+            $void = Koha::Account::Line->new(
+                {
+                    borrowernumber    => $self->borrowernumber,
+                    date              => \'NOW()',
+                    debit_type_code   => 'VOID',
+                    amount            => $self->amount * -1,
+                    amountoutstanding => $self->amount * -1,
+                    manager_id        => $params->{staff_id},
+                    interface         => $params->{interface},
+                    branchcode        => $params->{branch},
+                }
+            )->store();
+
+            # Record the creation offset
+            Koha::Account::Offset->new(
+                {
+                    debit_id => $void->id,
+                    type     => 'CREATE',
+                    amount   => $self->amount * -1
+                }
+            )->store();
+
+            # Link void to payment
+            $self->set({
+                amountoutstanding => $self->amount,
+                status => 'VOID'
+            })->store();
+            $self->apply( { debits => [$void] } );
+
+            # Reverse any applied payments
             foreach my $account_offset (@account_offsets) {
                 my $fee_paid =
                   Koha::Account::Lines->find( $account_offset->debit_id );
@@ -246,7 +312,7 @@ sub void {
                         credit_id => $self->id,
                         debit_id  => $fee_paid->id,
                         amount    => $amount_paid,
-                        type      => 'Void Payment',
+                        type      => 'VOID',
                     }
                 )->store();
             }
@@ -273,18 +339,11 @@ sub void {
                     )
                 );
             }
-
-            $self->set(
-                {
-                    status            => 'VOID',
-                    amountoutstanding => 0,
-                    amount            => 0,
-                }
-            );
-            $self->store();
         }
     );
 
+    $void->discard_changes;
+    return $void;
 }
 
 =head3 cancel
@@ -293,46 +352,85 @@ sub void {
 
 Cancel a charge. It will mark the debit as 'cancelled' by updating its
 status to 'CANCELLED'.
+
 Charges that have been fully or partially paid cannot be cancelled.
 
-Return self in case of success, undef otherwise
+Returns the cancellation accountline.
 
 =cut
 
 sub cancel {
-    my ($self) = @_;
+    my ( $self, $params ) = @_;
 
-    # Make sure it is a charge we are cancelling
-    return unless $self->is_debit;
+    # Make sure it is a charge we are reducing
+    unless ( $self->is_debit ) {
+        Koha::Exceptions::Account::IsNotDebit->throw(
+            error => 'Account line ' . $self->id . 'is not a debit' );
+    }
+    if ( $self->debit_type_code eq 'PAYOUT' ) {
+        Koha::Exceptions::Account::IsNotDebit->throw(
+            error => 'Account line ' . $self->id . 'is a payout' );
+    }
 
     # Make sure it is not already cancelled
-    return if $self->status && $self->status eq 'CANCELLED';
+    if ( $self->status && $self->status eq 'CANCELLED' ) {
+        Koha::Exceptions::Account->throw(
+            error => 'Account line ' . $self->id . 'is already cancelled' );
+    }
 
     # Make sure it has not be paid yet
-    return if $self->amount != $self->amountoutstanding;
-
-    if ( C4::Context->preference("FinesLog") ) {
-        logaction('FINES', 'CANCEL', $self->borrowernumber, Dumper({
-            action => 'cancel_charge',
-            borrowernumber => $self->borrowernumber,
-            amount => $self->amount,
-            amountoutstanding => $self->amountoutstanding,
-            description => $self->description,
-            debit_type_code => $self->debit_type_code,
-            note => $self->note,
-            itemnumber => $self->itemnumber,
-            manager_id => $self->manager_id,
-        }));
+    if ( $self->amount != $self->amountoutstanding ) {
+        Koha::Exceptions::Account->throw(
+            error => 'Account line ' . $self->id . 'is already offset' );
     }
 
-    $self->set({
-        status => 'CANCELLED',
-        amountoutstanding => 0,
-        amount => 0,
-    });
-    $self->store();
+    # Check for mandatory parameters
+    my @mandatory = ( 'staff_id', 'branch' );
+    for my $param (@mandatory) {
+        unless ( defined( $params->{$param} ) ) {
+            Koha::Exceptions::MissingParameter->throw(
+                error => "The $param parameter is mandatory" );
+        }
+    }
 
-    return $self;
+    my $cancellation;
+    $self->_result->result_source->schema->txn_do(
+        sub {
+
+            # A 'cancellation' is a 'credit'
+            $cancellation = Koha::Account::Line->new(
+                {
+                    date              => \'NOW()',
+                    amount            => 0 - $self->amount,
+                    credit_type_code  => 'CANCELLATION',
+                    status            => 'ADDED',
+                    amountoutstanding => 0 - $self->amount,
+                    manager_id        => $params->{staff_id},
+                    borrowernumber    => $self->borrowernumber,
+                    interface         => 'intranet',
+                    branchcode        => $params->{branch},
+                }
+            )->store();
+
+            my $cancellation_offset = Koha::Account::Offset->new(
+                {
+                    credit_id => $cancellation->accountlines_id,
+                    type      => 'CREATE',
+                    amount    => 0 - $self->amount
+                }
+            )->store();
+
+            # Link cancellation to charge
+            $cancellation->apply( { debits => [$self] } );
+            $cancellation->status('APPLIED')->store();
+
+            # Update status of original debit
+            $self->status('CANCELLED')->store;
+        }
+    );
+
+    $cancellation->discard_changes;
+    return $cancellation;
 }
 
 =head3 reduce
@@ -436,8 +534,8 @@ sub reduce {
             my $reduction_offset = Koha::Account::Offset->new(
                 {
                     credit_id => $reduction->accountlines_id,
-                    type      => uc( $params->{reduction_type} ),
-                    amount    => $params->{amount}
+                    type      => 'CREATE',
+                    amount    => 0 - $params->{amount}
                 }
             )->store();
 
@@ -445,22 +543,18 @@ sub reduce {
             my $debit_outstanding = $self->amountoutstanding;
             if ( $debit_outstanding >= $params->{amount} ) {
 
-                $reduction->apply(
-                    {
-                        debits      => [$self],
-                        offset_type => uc( $params->{reduction_type} )
-                    }
-                );
+                $reduction->apply( { debits => [$self] } );
                 $reduction->status('APPLIED')->store();
             }
             else {
 
-        # Zero amount offset used to link original 'debit' to reduction 'credit'
+                # Zero amount offset used to link original 'debit' to
+                # reduction 'credit'
                 my $link_reduction_offset = Koha::Account::Offset->new(
                     {
                         credit_id => $reduction->accountlines_id,
                         debit_id  => $self->accountlines_id,
-                        type      => uc( $params->{reduction_type} ),
+                        type      => 'APPLY',
                         amount    => 0
                     }
                 )->store();
@@ -478,7 +572,7 @@ sub reduce {
 =head3 apply
 
     my $debits = $account->outstanding_debits;
-    my $outstanding_amount = $credit->apply( { debits => $debits, [ offset_type => $offset_type ] } );
+    my $credit = $credit->apply( { debits => $debits } );
 
 Applies the credit to a given debits array reference.
 
@@ -488,9 +582,6 @@ Applies the credit to a given debits array reference.
 
 =item debits - Koha::Account::Lines object set of debits
 
-=item offset_type (optional) - a string indicating the offset type (valid values are those from
-the 'account_offset_types' table)
-
 =back
 
 =cut
@@ -499,7 +590,6 @@ sub apply {
     my ( $self, $params ) = @_;
 
     my $debits      = $params->{debits};
-    my $offset_type = $params->{offset_type} // 'Credit Applied';
 
     unless ( $self->is_credit ) {
         Koha::Exceptions::Account::IsNotCredit->throw(
@@ -540,7 +630,7 @@ sub apply {
                 {   credit_id => $self->id,
                     debit_id  => $debit->id,
                     amount    => $amount_to_cancel * -1,
-                    type      => $offset_type,
+                    type      => 'APPLY'
                 }
             )->store();
 
@@ -551,9 +641,17 @@ sub apply {
 
             # Attempt to renew the item associated with this debit if
             # appropriate
-            if ($debit->renewable) {
-                $debit->renew_item($params->{interface});
+            if ( $self->credit_type_code ne 'FORGIVEN' && $debit->is_renewable ) {
+                my $outcome = $debit->renew_item( { interface => $params->{interface} } );
+                $self->add_message(
+                    {
+                        type    => 'info',
+                        message => 'renewal',
+                        payload => $outcome
+                    }
+                ) if $outcome;
             }
+            $debit->discard_changes; # Refresh values from DB to clear floating point remainders
 
             # Same logic exists in Koha::Account::pay
             if (
@@ -572,10 +670,12 @@ sub apply {
                 C4::Circulation::ReturnLostItem( $self->borrowernumber,
                     $debit->itemnumber );
             }
+
+            last if $available_credit == 0;
         }
     });
 
-    return $available_credit;
+    return $self;
 }
 
 =head3 payout
@@ -658,12 +758,12 @@ sub payout {
             my $payout_offset = Koha::Account::Offset->new(
                 {
                     debit_id => $payout->accountlines_id,
-                    type     => 'PAYOUT',
+                    type     => 'CREATE',
                     amount   => $amount
                 }
             )->store();
 
-            $self->apply( { debits => [$payout], offset_type => 'PAYOUT' } );
+            $self->apply( { debits => [$payout] } );
             $self->status('PAID')->store;
         }
     );
@@ -829,6 +929,7 @@ on the API.
 sub to_api_mapping {
     return {
         accountlines_id   => 'account_line_id',
+        credit_number     => undef,
         credit_type_code  => 'credit_type',
         debit_type_code   => 'debit_type',
         amountoutstanding => 'amount_outstanding',
@@ -838,17 +939,18 @@ sub to_api_mapping {
         itemnumber        => 'item_id',
         manager_id        => 'user_id',
         note              => 'internal_note',
+        register_id       => 'cash_register_id',
     };
 
 }
 
-=head3 renewable
+=head3 is_renewable
 
-    my $bool = $line->renewable;
+    my $bool = $line->is_renewable;
 
 =cut
 
-sub renewable {
+sub is_renewable {
     my ($self) = @_;
 
     return (
@@ -856,7 +958,9 @@ sub renewable {
         $self->debit_type_code &&
         $self->debit_type_code eq 'OVERDUE' &&
         $self->status &&
-        $self->status eq 'UNRETURNED'
+        $self->status eq 'UNRETURNED' &&
+        $self->item &&
+        $self->patron
     ) ? 1 : 0;
 }
 
@@ -865,7 +969,8 @@ sub renewable {
     my $renew_result = $line->renew_item;
 
 Conditionally attempt to renew an item and return the outcome. This is
-as a consequence of the fine on an item being fully paid off
+as a consequence of the fine on an item being fully paid off.
+Caller must call is_renewable before.
 
 =cut
 
@@ -874,19 +979,14 @@ sub renew_item {
 
     my $outcome = {};
 
-    # We want to reject the call to renew if any of these apply:
+    # We want to reject the call to renew if:
     # - The RenewAccruingItemWhenPaid syspref is off
-    # - The line item doesn't have an item attached to it
-    # - The line item doesn't have a patron attached to it
-    #
+    # OR
     # - The RenewAccruingItemInOpac syspref is off
-    # AND
     # - There is an interface param passed and it's value is 'opac'
 
     if (
         !C4::Context->preference('RenewAccruingItemWhenPaid') ||
-        !$self->item ||
-        !$self->patron ||
         (
             !C4::Context->preference('RenewAccruingItemInOpac') &&
             $params->{interface} &&