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