View | Details | Raw Unified | Return to bug 23442
Collapse All | Expand All

(-)a/Koha/Account/Line.pm (+135 lines)
Lines 270-275 sub void { Link Here
270
270
271
}
271
}
272
272
273
=head3 reduce
274
275
  $charge_accountline->reduce({
276
      reduction_type => $reduction_type
277
  });
278
279
Used to 'reduce' a charge/debit by adding a credit to offset against the amount
280
outstanding.
281
282
May be used to apply a discount whilst retaining the original debit amounts or
283
to apply a full or partial refund for example when a lost item is found and
284
returned.
285
286
It will immediately be applied to the given debit unless the debit has already
287
been paid, in which case a 'zero' offset will be added to maintain a link to
288
the debit but the outstanding credit will be left so it may be applied to other
289
debts.
290
291
Reduction type may be one of:
292
293
* DISCOUNT
294
* REFUND
295
296
Returns the reduction accountline (which will be a credit)
297
298
=cut
299
300
sub reduce {
301
    my ( $self, $params ) = @_;
302
303
    # Make sure it is a charge we are reducing
304
    unless ( $self->is_debit ) {
305
        Koha::Exceptions::Account::IsNotDebit->throw(
306
            error => 'Account line ' . $self->id . 'is not a debit' );
307
    }
308
309
    # Check for mandatory parameters
310
    my @mandatory = ( 'interface', 'reduction_type', 'amount' );
311
    for my $param (@mandatory) {
312
        unless ( defined( $params->{$param} ) ) {
313
            Koha::Exceptions::MissingParameter->throw(
314
                error => "The $param parameter is mandatory" );
315
        }
316
    }
317
318
    # More mandatory parameters
319
    if ( $params->{interface} eq 'intranet' ) {
320
        my @optional = ( 'staff_id', 'branch' );
321
        for my $param (@optional) {
322
            unless ( defined( $params->{$param} ) ) {
323
                Koha::Exceptions::MissingParameter->throw( error =>
324
"The $param parameter is mandatory when interface is set to 'intranet'"
325
                );
326
            }
327
        }
328
    }
329
330
    # Make sure the reduction isn't more than the original
331
    my $original = $self->amount;
332
    Koha::Exceptions::Account::AmountNotPositive->throw(
333
        error => 'Reduce amount passed is not positive' )
334
      unless ( $params->{amount} > 0 );
335
    Koha::Exceptions::ParameterTooHigh->throw( error =>
336
"Amount to reduce ($params->{amount}) is higher than original amount ($original)"
337
    ) unless ( $original >= $params->{amount} );
338
    my $reduced =
339
      $self->credits( { credit_type_code => [ 'DISCOUNT', 'REFUND' ] } )->total;
340
    Koha::Exceptions::ParameterTooHigh->throw( error =>
341
"Combined reduction ($params->{amount} + $reduced) is higher than original amount ("
342
          . abs($original)
343
          . ")" )
344
      unless ( $original >= ( $params->{amount} + abs($reduced) ) );
345
346
    my $status = { 'REFUND' => 'REFUNDED', 'DISCOUNT' => 'DISCOUNTED' };
347
348
    my $reduction;
349
    $self->_result->result_source->schema->txn_do(
350
        sub {
351
352
            # A 'reduction' is a 'credit'
353
            $reduction = Koha::Account::Line->new(
354
                {
355
                    date              => \'NOW()',
356
                    amount            => 0 - $params->{amount},
357
                    credit_type_code  => $params->{reduction_type},
358
                    status            => 'ADDED',
359
                    amountoutstanding => 0 - $params->{amount},
360
                    manager_id        => $params->{staff_id},
361
                    borrowernumber    => $self->borrowernumber,
362
                    interface         => $params->{interface},
363
                    branchcode        => $params->{branch},
364
                }
365
            )->store();
366
367
            my $reduction_offset = Koha::Account::Offset->new(
368
                {
369
                    credit_id => $reduction->accountlines_id,
370
                    type      => uc( $params->{reduction_type} ),
371
                    amount    => $params->{amount}
372
                }
373
            )->store();
374
375
            # Link reduction to charge (and apply as required)
376
            my $debit_outstanding = $self->amountoutstanding;
377
            if ( $debit_outstanding >= $params->{amount} ) {
378
379
                $reduction->apply(
380
                    {
381
                        debits      => [$self],
382
                        offset_type => $params->{reduction_type}
383
                    }
384
                );
385
                $reduction->status('APPLIED')->store();
386
            }
387
            else {
388
389
        # Zero amount offset used to link original 'debit' to reduction 'credit'
390
                my $link_reduction_offset = Koha::Account::Offset->new(
391
                    {
392
                        credit_id => $reduction->accountlines_id,
393
                        debit_id  => $self->accountlines_id,
394
                        type      => $params->{reduction_type},
395
                        amount    => 0
396
                    }
397
                )->store();
398
            }
399
400
            # Update status of original debit
401
            $self->status( $status->{ $params->{reduction_type} } )->store;
402
        }
403
    );
404
405
    return $reduction->discard_changes;
406
}
407
273
=head3 apply
408
=head3 apply
274
409
275
    my $debits = $account->outstanding_debits;
410
    my $debits = $account->outstanding_debits;
(-)a/t/db_dependent/Koha/Account/Lines.t (-2 / +159 lines)
Lines 19-25 Link Here
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
21
22
use Test::More tests => 14;
22
use Test::More tests => 15;
23
use Test::Exception;
23
use Test::Exception;
24
24
25
use C4::Circulation qw/AddIssue AddReturn/;
25
use C4::Circulation qw/AddIssue AddReturn/;
Lines 1029-1032 subtest "payout() tests" => sub { Link Here
1029
    $schema->storage->txn_rollback;
1029
    $schema->storage->txn_rollback;
1030
};
1030
};
1031
1031
1032
subtest "reduce() tests" => sub {
1033
1034
    plan tests => 25;
1035
1036
    $schema->storage->txn_begin;
1037
1038
    # Create a borrower
1039
    my $categorycode =
1040
      $builder->build( { source => 'Category' } )->{categorycode};
1041
    my $branchcode = $builder->build( { source => 'Branch' } )->{branchcode};
1042
1043
    my $borrower = Koha::Patron->new(
1044
        {
1045
            cardnumber => 'dariahall',
1046
            surname    => 'Hall',
1047
            firstname  => 'Daria',
1048
        }
1049
    );
1050
    $borrower->categorycode($categorycode);
1051
    $borrower->branchcode($branchcode);
1052
    $borrower->store;
1053
1054
    my $staff = Koha::Patron->new(
1055
        {
1056
            cardnumber => 'bobby',
1057
            surname    => 'Bloggs',
1058
            firstname  => 'Bobby',
1059
        }
1060
    );
1061
    $staff->categorycode($categorycode);
1062
    $staff->branchcode($branchcode);
1063
    $staff->store;
1064
1065
    my $account = Koha::Account->new( { patron_id => $borrower->id } );
1066
1067
    my $debit1 = Koha::Account::Line->new(
1068
        {
1069
            borrowernumber    => $borrower->borrowernumber,
1070
            amount            => 20,
1071
            amountoutstanding => 20,
1072
            interface         => 'commandline',
1073
            debit_type_code   => 'LOST'
1074
        }
1075
    )->store();
1076
    my $credit1 = Koha::Account::Line->new(
1077
        {
1078
            borrowernumber    => $borrower->borrowernumber,
1079
            amount            => -20,
1080
            amountoutstanding => -20,
1081
            interface         => 'commandline',
1082
            credit_type_code  => 'CREDIT'
1083
        }
1084
    )->store();
1085
1086
    is( $account->balance(), 0, "Account balance is 0" );
1087
    is( $debit1->amountoutstanding,
1088
        20, 'Overdue fee has an amount outstanding of 20' );
1089
    is( $credit1->amountoutstanding,
1090
        -20, 'Credit has an amount outstanding of -20' );
1091
1092
    my $reduce_params = {
1093
        interface      => 'commandline',
1094
        reduction_type => 'REFUND',
1095
        amount         => 5,
1096
        staff_id       => $staff->borrowernumber,
1097
        branch         => $branchcode
1098
    };
1099
1100
    throws_ok { $credit1->reduce($reduce_params); }
1101
    'Koha::Exceptions::Account::IsNotDebit',
1102
      '->reduce() can only be used with debits';
1103
1104
    my @required = ( 'interface', 'reduction_type', 'amount' );
1105
    for my $required (@required) {
1106
        my $params = {%$reduce_params};
1107
        delete( $params->{$required} );
1108
        throws_ok {
1109
            $debit1->reduce($params);
1110
        }
1111
        'Koha::Exceptions::MissingParameter',
1112
          "->reduce() requires the `$required` parameter is passed";
1113
    }
1114
1115
    $reduce_params->{interface} = 'intranet';
1116
    my @dependant_required = ( 'staff_id', 'branch' );
1117
    for my $d (@dependant_required) {
1118
        my $params = {%$reduce_params};
1119
        delete( $params->{$d} );
1120
        throws_ok {
1121
            $debit1->reduce($params);
1122
        }
1123
        'Koha::Exceptions::MissingParameter',
1124
"->reduce() requires the `$d` parameter is passed when interface is intranet";
1125
    }
1126
1127
    throws_ok {
1128
        $debit1->reduce(
1129
            {
1130
                interface      => 'intranet',
1131
                staff_id       => $staff->borrowernumber,
1132
                branch         => $branchcode,
1133
                reduction_type => 'REFUND',
1134
                amount         => 25
1135
            }
1136
        );
1137
    }
1138
    'Koha::Exceptions::ParameterTooHigh',
1139
      '->reduce() cannot reduce more than original amount';
1140
1141
    # Partial Reduction 
1142
    # (Refund 5 on debt of 20)
1143
    my $reduction = $debit1->reduce($reduce_params);
1144
1145
    is( $reduction->amount() * 1, -5, "Reduce amount is -5" );
1146
    is( $reduction->amountoutstanding() * 1,
1147
        0, "Reduce amountoutstanding is 0" );
1148
    is( $debit1->amountoutstanding() * 1,
1149
        15, "Debit amountoutstanding reduced by 5 to 15" );
1150
    is( $account->balance() * 1, -5,       "Account balance is -5" );
1151
    is( $reduction->status(),    'APPLIED', "Reduction status is 'APPLIED'" );
1152
1153
    my $offsets = Koha::Account::Offsets->search(
1154
        { credit_id => $reduction->id, debit_id => $debit1->id } );
1155
    is( $offsets->count, 1, 'Only one offset is generated' );
1156
    my $THE_offset = $offsets->next;
1157
    is( $THE_offset->amount * 1,
1158
        -5, 'Correct amount was applied against debit' );
1159
    is( $THE_offset->type, 'REFUND', "Offset type set to 'REFUND'" );
1160
1161
    # Zero offset created when zero outstanding 
1162
    # (Refund another 5 on paid debt of 20)
1163
    $credit1->apply( { debits => [ $debit1 ] } );
1164
    is($debit1->amountoutstanding + 0, 0, 'Debit1 amountoutstanding reduced to 0');
1165
    $reduction = $debit1->reduce($reduce_params);
1166
    is( $reduction->amount() * 1, -5, "Reduce amount is -5" );
1167
    is( $reduction->amountoutstanding() * 1,
1168
        -5, "Reduce amountoutstanding is -5" );
1169
1170
    $offsets = Koha::Account::Offsets->search(
1171
        { credit_id => $reduction->id, debit_id => $debit1->id } );
1172
    is( $offsets->count, 1, 'Only one new offset is generated' );
1173
    $THE_offset = $offsets->next;
1174
    is( $THE_offset->amount * 1,
1175
        0, 'Zero offset created for already paid off debit' );
1176
    is( $THE_offset->type, 'REFUND', "Offset type set to 'REFUND'" );
1177
1178
    # Compound reduction should not allow more than original amount 
1179
    # (Reduction of 5 + 5 + 20 > 20)
1180
    $reduce_params->{amount} = 20;
1181
    throws_ok {
1182
        $debit1->reduce($reduce_params);
1183
    }
1184
    'Koha::Exceptions::ParameterTooHigh',
1185
'->reduce cannot reduce mor than the original amount (combined reductions test)';
1186
1187
    $schema->storage->txn_rollback;
1188
};
1189
1032
1;
1190
1;
1033
- 

Return to bug 23442