Bug 21683: (QA follow-up) Koha::Account::Line rebase fix
[koha-equinox.git] / Koha / Account / Line.pm
1 package Koha::Account::Line;
2
3 # This file is part of Koha.
4 #
5 # Koha is free software; you can redistribute it and/or modify it under the
6 # terms of the GNU General Public License as published by the Free Software
7 # Foundation; either version 3 of the License, or (at your option) any later
8 # version.
9 #
10 # Koha is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along
15 # with Koha; if not, write to the Free Software Foundation, Inc.,
16 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18 use Modern::Perl;
19
20 use Carp;
21 use Data::Dumper;
22
23 use C4::Log qw(logaction);
24
25 use Koha::Account::Offsets;
26 use Koha::Database;
27 use Koha::Exceptions::Account;
28 use Koha::Items;
29
30 use base qw(Koha::Object);
31
32 =encoding utf8
33
34 =head1 NAME
35
36 Koha::Account::Line - Koha accountline Object class
37
38 =head1 API
39
40 =head2 Class methods
41
42 =cut
43
44 =head3 item
45
46 Return the item linked to this account line if exists
47
48 =cut
49
50 sub item {
51     my ( $self ) = @_;
52     my $rs = $self->_result->itemnumber;
53     return unless $rs;
54     return Koha::Item->_new_from_dbic( $rs );
55 }
56
57 =head3 checkout
58
59 Return the checkout linked to this account line if exists
60
61 =cut
62
63 sub checkout {
64     my ( $self ) = @_;
65     return unless $self->issue_id ;
66
67     $self->{_checkout} ||= Koha::Checkouts->find( $self->issue_id );
68     $self->{_checkout} ||= Koha::Old::Checkouts->find( $self->issue_id );
69     return $self->{_checkout};
70 }
71
72 =head3 void
73
74 $payment_accountline->void();
75
76 =cut
77
78 sub void {
79     my ($self) = @_;
80
81     # Make sure it is a payment we are voiding
82     return unless $self->amount < 0;
83
84     my @account_offsets =
85       Koha::Account::Offsets->search(
86         { credit_id => $self->id, amount => { '<' => 0 }  } );
87
88     $self->_result->result_source->schema->txn_do(
89         sub {
90             foreach my $account_offset (@account_offsets) {
91                 my $fee_paid =
92                   Koha::Account::Lines->find( $account_offset->debit_id );
93
94                 next unless $fee_paid;
95
96                 my $amount_paid = $account_offset->amount * -1; # amount paid is stored as a negative amount
97                 my $new_amount = $fee_paid->amountoutstanding + $amount_paid;
98                 $fee_paid->amountoutstanding($new_amount);
99                 $fee_paid->store();
100
101                 Koha::Account::Offset->new(
102                     {
103                         credit_id => $self->id,
104                         debit_id  => $fee_paid->id,
105                         amount    => $amount_paid,
106                         type      => 'Void Payment',
107                     }
108                 )->store();
109             }
110
111             if ( C4::Context->preference("FinesLog") ) {
112                 logaction(
113                     "FINES", 'VOID',
114                     $self->borrowernumber,
115                     Dumper(
116                         {
117                             action         => 'void_payment',
118                             borrowernumber => $self->borrowernumber,
119                             amount            => $self->amount,
120                             amountoutstanding => $self->amountoutstanding,
121                             description       => $self->description,
122                             accounttype       => $self->accounttype,
123                             payment_type      => $self->payment_type,
124                             note              => $self->note,
125                             itemnumber        => $self->itemnumber,
126                             manager_id        => $self->manager_id,
127                             offsets =>
128                               [ map { $_->unblessed } @account_offsets ],
129                         }
130                     )
131                 );
132             }
133
134             $self->set(
135                 {
136                     accounttype       => 'VOID',
137                     amountoutstanding => 0,
138                     amount            => 0,
139                 }
140             );
141             $self->store();
142         }
143     );
144
145 }
146
147 =head3 apply
148
149     my $debits = $account->outstanding_debits;
150     my $outstanding_amount = $credit->apply( { debits => $debits, [ offset_type => $offset_type ] } );
151
152 Applies the credit to a given debits set.
153
154 =head4 arguments hashref
155
156 =over 4
157
158 =item debits - Koha::Account::Lines object set of debits
159
160 =item offset_type (optional) - a string indicating the offset type (valid values are those from
161 the 'account_offset_types' table)
162
163 =back
164
165 =cut
166
167 sub apply {
168     my ( $self, $params ) = @_;
169
170     my $debits      = $params->{debits};
171     my $offset_type = $params->{offset_type} // 'Credit Applied';
172
173     unless ( $self->is_credit ) {
174         Koha::Exceptions::Account::IsNotCredit->throw(
175             error => 'Account line ' . $self->id . ' is not a credit'
176         );
177     }
178
179     my $available_credit = $self->amountoutstanding * -1;
180
181     unless ( $available_credit > 0 ) {
182         Koha::Exceptions::Account::NoAvailableCredit->throw(
183             error => 'Outstanding credit is ' . $available_credit . ' and cannot be applied'
184         );
185     }
186
187     my $schema = Koha::Database->new->schema;
188
189     $schema->txn_do( sub {
190         while ( my $debit = $debits->next ) {
191
192             unless ( $debit->is_debit ) {
193                 Koha::Exceptions::Account::IsNotDebit->throw(
194                     error => 'Account line ' . $debit->id . 'is not a debit'
195                 );
196             }
197             my $amount_to_cancel;
198             my $owed = $debit->amountoutstanding;
199
200             if ( $available_credit >= $owed ) {
201                 $amount_to_cancel = $owed;
202             }
203             else {    # $available_credit < $debit->amountoutstanding
204                 $amount_to_cancel = $available_credit;
205             }
206
207             # record the account offset
208             Koha::Account::Offset->new(
209                 {   credit_id => $self->id,
210                     debit_id  => $debit->id,
211                     amount    => $amount_to_cancel * -1,
212                     type      => $offset_type,
213                 }
214             )->store();
215
216             $available_credit -= $amount_to_cancel;
217
218             $self->amountoutstanding( $available_credit * -1 )->store;
219             $debit->amountoutstanding( $owed - $amount_to_cancel )->store;
220         }
221     });
222
223     return $available_credit;
224 }
225
226 =head3 adjust
227
228 This method allows updating a debit or credit on a patron's account
229
230     $account_line->adjust(
231         {
232             amount => $amount,
233             type   => $update_type,
234         }
235     );
236
237 $update_type can be any of:
238   - fine_update
239
240 Authors Note: The intention here is that this method is only used
241 to adjust accountlines where the final amount is not yet known/fixed.
242 Incrementing fines are the only existing case at the time of writing,
243 all other forms of 'adjustment' should be recorded as distinct credits
244 or debits and applied, via an offset, to the corresponding debit or credit.
245
246 =cut
247
248 sub adjust {
249     my ( $self, $params ) = @_;
250
251     my $amount       = $params->{amount};
252     my $update_type  = $params->{type};
253
254     unless ( exists($Koha::Account::Line::allowed_update->{$update_type}) ) {
255         Koha::Exceptions::Account::UnrecognisedType->throw(
256             error => 'Update type not recognised'
257         );
258     }
259
260     my $account_type = $self->accounttype;
261     unless ( $Koha::Account::Line::allowed_update->{$update_type} eq $account_type ) {
262         Koha::Exceptions::Account::UnrecognisedType->throw(
263             error => 'Update type not allowed on this accounttype'
264         );
265     }
266
267     my $schema = Koha::Database->new->schema;
268
269     $schema->txn_do(
270         sub {
271
272             my $amount_before             = $self->amount;
273             my $amount_outstanding_before = $self->amountoutstanding;
274             my $difference                = $amount - $amount_before;
275             my $new_outstanding           = $amount_outstanding_before + $difference;
276
277             my $offset_type = substr( $update_type, 0, index( $update_type, '_' ) );
278             $offset_type .= ( $difference > 0 ) ? "_increase" : "_decrease";
279
280             # Catch cases that require patron refunds
281             if ( $new_outstanding < 0 ) {
282                 my $account =
283                   Koha::Patrons->find( $self->borrowernumber )->account;
284                 my $credit = $account->add_credit(
285                     {
286                         amount      => $new_outstanding * -1,
287                         description => 'Overpayment refund',
288                         type        => 'credit',
289                         ( $update_type eq 'fine_update' ? ( item_id => $self->itemnumber ) : ()),
290                     }
291                 );
292                 $new_outstanding = 0;
293             }
294
295             # Update the account line
296             $self->set(
297                 {
298                     date              => \'NOW()',
299                     amount            => $amount,
300                     amountoutstanding => $new_outstanding,
301                     ( $update_type eq 'fine_update' ? ( lastincrement => $difference ) : ()),
302                 }
303             )->store();
304
305             # Record the account offset
306             my $account_offset = Koha::Account::Offset->new(
307                 {
308                     debit_id => $self->id,
309                     type     => $offset_type,
310                     amount   => $difference
311                 }
312             )->store();
313
314             if ( C4::Context->preference("FinesLog") ) {
315                 logaction(
316                     "FINES", 'UPDATE', #undef becomes UPDATE in UpdateFine
317                     $self->borrowernumber,
318                     Dumper(
319                         {   action            => $update_type,
320                             borrowernumber    => $self->borrowernumber,
321                             amount            => $amount,
322                             description       => undef,
323                             amountoutstanding => $new_outstanding,
324                             accounttype       => $self->accounttype,
325                             note              => undef,
326                             itemnumber        => $self->itemnumber,
327                             manager_id        => undef,
328                         }
329                     )
330                 ) if ( $update_type eq 'fine_update' );
331             }
332         }
333     );
334
335     return $self;
336 }
337
338 =head3 is_credit
339
340     my $bool = $line->is_credit;
341
342 =cut
343
344 sub is_credit {
345     my ($self) = @_;
346
347     return ( $self->amount < 0 );
348 }
349
350 =head3 is_debit
351
352     my $bool = $line->is_debit;
353
354 =cut
355
356 sub is_debit {
357     my ($self) = @_;
358
359     return !$self->is_credit;
360 }
361
362 =head2 Internal methods
363
364 =cut
365
366 =head3 _type
367
368 =cut
369
370 sub _type {
371     return 'Accountline';
372 }
373
374 1;
375
376 =head2 Name mappings
377
378 =head3 $allowed_update
379
380 =cut
381
382 our $allowed_update = { 'fine_update' => 'FU', };
383
384 =head1 AUTHORS
385
386 Kyle M Hall <kyle@bywatersolutions.com >
387 Tomás Cohen Arazi <tomascohen@theke.io>
388 Martin Renvoize <martin.renvoize@ptfs-europe.com>
389
390 =cut