6443b4d3464bc25eef7daa1327e94cae6061e083
[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             interface => $interface
235         }
236     );
237
238 $update_type can be any of:
239   - fine_update
240
241 Authors Note: The intention here is that this method is only used
242 to adjust accountlines where the final amount is not yet known/fixed.
243 Incrementing fines are the only existing case at the time of writing,
244 all other forms of 'adjustment' should be recorded as distinct credits
245 or debits and applied, via an offset, to the corresponding debit or credit.
246
247 =cut
248
249 sub adjust {
250     my ( $self, $params ) = @_;
251
252     my $amount       = $params->{amount};
253     my $update_type  = $params->{type};
254     my $interface    = $params->{interface};
255
256     unless ( exists($Koha::Account::Line::allowed_update->{$update_type}) ) {
257         Koha::Exceptions::Account::UnrecognisedType->throw(
258             error => 'Update type not recognised'
259         );
260     }
261
262     my $account_type = $self->accounttype;
263     unless ( $Koha::Account::Line::allowed_update->{$update_type} eq $account_type ) {
264         Koha::Exceptions::Account::UnrecognisedType->throw(
265             error => 'Update type not allowed on this accounttype'
266         );
267     }
268
269     my $schema = Koha::Database->new->schema;
270
271     $schema->txn_do(
272         sub {
273
274             my $amount_before             = $self->amount;
275             my $amount_outstanding_before = $self->amountoutstanding;
276             my $difference                = $amount - $amount_before;
277             my $new_outstanding           = $amount_outstanding_before + $difference;
278
279             my $offset_type = substr( $update_type, 0, index( $update_type, '_' ) );
280             $offset_type .= ( $difference > 0 ) ? "_increase" : "_decrease";
281
282             # Catch cases that require patron refunds
283             if ( $new_outstanding < 0 ) {
284                 my $account =
285                   Koha::Patrons->find( $self->borrowernumber )->account;
286                 my $credit = $account->add_credit(
287                     {
288                         amount      => $new_outstanding * -1,
289                         description => 'Overpayment refund',
290                         type        => 'credit',
291                         interface   => $interface,
292                         ( $update_type eq 'fine_update' ? ( item_id => $self->itemnumber ) : ()),
293                     }
294                 );
295                 $new_outstanding = 0;
296             }
297
298             # Update the account line
299             $self->set(
300                 {
301                     date              => \'NOW()',
302                     amount            => $amount,
303                     amountoutstanding => $new_outstanding
304                 }
305             )->store();
306
307             # Record the account offset
308             my $account_offset = Koha::Account::Offset->new(
309                 {
310                     debit_id => $self->id,
311                     type     => $offset_type,
312                     amount   => $difference
313                 }
314             )->store();
315
316             if ( C4::Context->preference("FinesLog") ) {
317                 logaction(
318                     "FINES", 'UPDATE', #undef becomes UPDATE in UpdateFine
319                     $self->borrowernumber,
320                     Dumper(
321                         {   action            => $update_type,
322                             borrowernumber    => $self->borrowernumber,
323                             amount            => $amount,
324                             description       => undef,
325                             amountoutstanding => $new_outstanding,
326                             accounttype       => $self->accounttype,
327                             note              => undef,
328                             itemnumber        => $self->itemnumber,
329                             manager_id        => undef,
330                         }
331                     )
332                 ) if ( $update_type eq 'fine_update' );
333             }
334         }
335     );
336
337     return $self;
338 }
339
340 =head3 is_credit
341
342     my $bool = $line->is_credit;
343
344 =cut
345
346 sub is_credit {
347     my ($self) = @_;
348
349     return ( $self->amount < 0 );
350 }
351
352 =head3 is_debit
353
354     my $bool = $line->is_debit;
355
356 =cut
357
358 sub is_debit {
359     my ($self) = @_;
360
361     return !$self->is_credit;
362 }
363
364 =head2 Internal methods
365
366 =cut
367
368 =head3 _type
369
370 =cut
371
372 sub _type {
373     return 'Accountline';
374 }
375
376 1;
377
378 =head2 Name mappings
379
380 =head3 $allowed_update
381
382 =cut
383
384 our $allowed_update = { 'fine_update' => 'FU', };
385
386 =head1 AUTHORS
387
388 Kyle M Hall <kyle@bywatersolutions.com >
389 Tomás Cohen Arazi <tomascohen@theke.io>
390 Martin Renvoize <martin.renvoize@ptfs-europe.com>
391
392 =cut