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