Bug 21727: (QA follow-up) Fix POD encoding
[koha.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 Koha::Item->_new_from_dbic( $rs );
54 }
55
56 =head3 void
57
58 $payment_accountline->void();
59
60 =cut
61
62 sub void {
63     my ($self) = @_;
64
65     # Make sure it is a payment we are voiding
66     return unless $self->amount < 0;
67
68     my @account_offsets =
69       Koha::Account::Offsets->search(
70         { credit_id => $self->id, amount => { '<' => 0 }  } );
71
72     $self->_result->result_source->schema->txn_do(
73         sub {
74             foreach my $account_offset (@account_offsets) {
75                 my $fee_paid =
76                   Koha::Account::Lines->find( $account_offset->debit_id );
77
78                 next unless $fee_paid;
79
80                 my $amount_paid = $account_offset->amount * -1; # amount paid is stored as a negative amount
81                 my $new_amount = $fee_paid->amountoutstanding + $amount_paid;
82                 $fee_paid->amountoutstanding($new_amount);
83                 $fee_paid->store();
84
85                 Koha::Account::Offset->new(
86                     {
87                         credit_id => $self->id,
88                         debit_id  => $fee_paid->id,
89                         amount    => $amount_paid,
90                         type      => 'Void Payment',
91                     }
92                 )->store();
93             }
94
95             if ( C4::Context->preference("FinesLog") ) {
96                 logaction(
97                     "FINES", 'VOID',
98                     $self->borrowernumber,
99                     Dumper(
100                         {
101                             action         => 'void_payment',
102                             borrowernumber => $self->borrowernumber,
103                             amount            => $self->amount,
104                             amountoutstanding => $self->amountoutstanding,
105                             description       => $self->description,
106                             accounttype       => $self->accounttype,
107                             payment_type      => $self->payment_type,
108                             note              => $self->note,
109                             itemnumber        => $self->itemnumber,
110                             manager_id        => $self->manager_id,
111                             offsets =>
112                               [ map { $_->unblessed } @account_offsets ],
113                         }
114                     )
115                 );
116             }
117
118             $self->set(
119                 {
120                     accounttype       => 'VOID',
121                     amountoutstanding => 0,
122                     amount            => 0,
123                 }
124             );
125             $self->store();
126         }
127     );
128
129 }
130
131 =head3 apply
132
133     my $debits = $account->outstanding_debits;
134     my $outstanding_amount = $credit->apply( { debits => $debits, [ offset_type => $offset_type ] } );
135
136 Applies the credit to a given debits set.
137
138 =head4 arguments hashref
139
140 =over 4
141
142 =item debits - Koha::Account::Lines object set of debits
143
144 =item offset_type (optional) - a string indicating the offset type (valid values are those from
145 the 'account_offset_types' table)
146
147 =back
148
149 =cut
150
151 sub apply {
152     my ( $self, $params ) = @_;
153
154     my $debits      = $params->{debits};
155     my $offset_type = $params->{offset_type} // 'Credit Applied';
156
157     unless ( $self->is_credit ) {
158         Koha::Exceptions::Account::IsNotCredit->throw(
159             error => 'Account line ' . $self->id . ' is not a credit'
160         );
161     }
162
163     my $available_credit = $self->amountoutstanding * -1;
164
165     unless ( $available_credit > 0 ) {
166         Koha::Exceptions::Account::NoAvailableCredit->throw(
167             error => 'Outstanding credit is ' . $available_credit . ' and cannot be applied'
168         );
169     }
170
171     my $schema = Koha::Database->new->schema;
172
173     $schema->txn_do( sub {
174         while ( my $debit = $debits->next ) {
175
176             unless ( $debit->is_debit ) {
177                 Koha::Exceptions::Account::IsNotDebit->throw(
178                     error => 'Account line ' . $debit->id . 'is not a debit'
179                 );
180             }
181             my $amount_to_cancel;
182             my $owed = $debit->amountoutstanding;
183
184             if ( $available_credit >= $owed ) {
185                 $amount_to_cancel = $owed;
186             }
187             else {    # $available_credit < $debit->amountoutstanding
188                 $amount_to_cancel = $available_credit;
189             }
190
191             # record the account offset
192             Koha::Account::Offset->new(
193                 {   credit_id => $self->id,
194                     debit_id  => $debit->id,
195                     amount    => $amount_to_cancel * -1,
196                     type      => $offset_type,
197                 }
198             )->store();
199
200             $available_credit -= $amount_to_cancel;
201
202             $self->amountoutstanding( $available_credit * -1 )->store;
203             $debit->amountoutstanding( $owed - $amount_to_cancel )->store;
204         }
205     });
206
207     return $available_credit;
208 }
209
210 =head3 adjust
211
212 This method allows updating a debit or credit on a patron's account
213
214     $account_line->adjust(
215         {
216             amount => $amount,
217             type   => $update_type,
218         }
219     );
220
221 $update_type can be any of:
222   - fine_increment
223
224 Authors Note: The intention here is that this method is only used
225 to adjust accountlines where the final amount is not yet known/fixed.
226 Incrementing fines are the only existing case at the time of writing,
227 all other forms of 'adjustment' should be recorded as distinct credits
228 or debits and applied, via an offset, to the corresponding debit or credit.
229
230 =cut
231
232 sub adjust {
233     my ( $self, $params ) = @_;
234
235     my $amount       = $params->{amount};
236     my $update_type  = $params->{type};
237
238     unless ( exists($Koha::Account::Line::offset_type->{$update_type}) ) {
239         Koha::Exceptions::Account::UnrecognisedType->throw(
240             error => 'Update type not recognised'
241         );
242     }
243
244     my $account_type = $self->accounttype;
245     unless ( $Koha::Account::Line::allowed_update->{$update_type} eq $account_type ) {
246         Koha::Exceptions::Account::UnrecognisedType->throw(
247             error => 'Update type not allowed on this accounttype'
248         );
249     }
250
251     my $schema = Koha::Database->new->schema;
252
253     $schema->txn_do(
254         sub {
255
256             my $amount_before             = $self->amount;
257             my $amount_outstanding_before = $self->amountoutstanding;
258             my $difference                = $amount - $amount_before;
259             my $new_outstanding           = $amount_outstanding_before + $difference;
260
261             # Catch cases that require patron refunds
262             if ( $new_outstanding < 0 ) {
263                 my $account =
264                   Koha::Patrons->find( $self->borrowernumber )->account;
265                 my $credit = $account->add_credit(
266                     {
267                         amount      => $new_outstanding * -1,
268                         description => 'Overpayment refund',
269                         type        => 'credit',
270                         ( $update_type eq 'fine_increment' ? ( item_id => $self->itemnumber ) : ()),
271                     }
272                 );
273                 $new_outstanding = 0;
274             }
275
276             # Update the account line
277             $self->set(
278                 {
279                     date              => \'NOW()',
280                     amount            => $amount,
281                     amountoutstanding => $new_outstanding,
282                     ( $update_type eq 'fine_increment' ? ( lastincrement => $difference ) : ()),
283                 }
284             )->store();
285
286             # Record the account offset
287             my $account_offset = Koha::Account::Offset->new(
288                 {
289                     debit_id => $self->id,
290                     type     => $Koha::Account::Line::offset_type->{$update_type},
291                     amount   => $difference
292                 }
293             )->store();
294
295             if ( C4::Context->preference("FinesLog") ) {
296                 logaction(
297                     "FINES", 'UPDATE', #undef becomes UPDATE in UpdateFine
298                     $self->borrowernumber,
299                     Dumper(
300                         {   action            => $update_type,
301                             borrowernumber    => $self->borrowernumber,
302                             accountno         => $self->accountno,
303                             amount            => $amount,
304                             description       => undef,
305                             amountoutstanding => $new_outstanding,
306                             accounttype       => $self->accounttype,
307                             note              => undef,
308                             itemnumber        => $self->itemnumber,
309                             manager_id        => undef,
310                         }
311                     )
312                 ) if ( $update_type eq 'fine_increment' );
313             }
314         }
315     );
316
317     return $self;
318 }
319
320 =head3 is_credit
321
322     my $bool = $line->is_credit;
323
324 =cut
325
326 sub is_credit {
327     my ($self) = @_;
328
329     return ( $self->amount < 0 );
330 }
331
332 =head3 is_debit
333
334     my $bool = $line->is_debit;
335
336 =cut
337
338 sub is_debit {
339     my ($self) = @_;
340
341     return !$self->is_credit;
342 }
343
344 =head2 Internal methods
345
346 =cut
347
348 =head3 _type
349
350 =cut
351
352 sub _type {
353     return 'Accountline';
354 }
355
356 1;
357
358 =head2 Name mappings
359
360 =head3 $offset_type
361
362 =cut
363
364 our $offset_type = { 'fine_increment' => 'Fine Update', };
365
366 =head3 $allowed_update
367
368 =cut
369
370 our $allowed_update = { 'fine_increment' => 'FU', };
371
372 =head1 AUTHORS
373
374 Kyle M Hall <kyle@bywatersolutions.com >
375 Tomás Cohen Arazi <tomascohen@theke.io>
376 Martin Renvoize <martin.renvoize@ptfs-europe.com>
377
378 =cut