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

(-)a/Koha/Account/Line.pm (+134 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
* REFUND
294
295
Returns the reduction accountline (which will be a credit)
296
297
=cut
298
299
sub reduce {
300
    my ( $self, $params ) = @_;
301
302
    # Make sure it is a charge we are reducing
303
    unless ( $self->is_debit ) {
304
        Koha::Exceptions::Account::IsNotDebit->throw(
305
            error => 'Account line ' . $self->id . 'is not a debit' );
306
    }
307
308
    # Check for mandatory parameters
309
    my @mandatory = ( 'interface', 'reduction_type', 'amount' );
310
    for my $param (@mandatory) {
311
        unless ( defined( $params->{$param} ) ) {
312
            Koha::Exceptions::MissingParameter->throw(
313
                error => "The $param parameter is mandatory" );
314
        }
315
    }
316
317
    # More mandatory parameters
318
    if ( $params->{interface} eq 'intranet' ) {
319
        my @optional = ( 'staff_id', 'branch' );
320
        for my $param (@optional) {
321
            unless ( defined( $params->{$param} ) ) {
322
                Koha::Exceptions::MissingParameter->throw( error =>
323
"The $param parameter is mandatory when interface is set to 'intranet'"
324
                );
325
            }
326
        }
327
    }
328
329
    # Make sure the reduction isn't more than the original
330
    my $original = $self->amount;
331
    Koha::Exceptions::Account::AmountNotPositive->throw(
332
        error => 'Reduce amount passed is not positive' )
333
      unless ( $params->{amount} > 0 );
334
    Koha::Exceptions::ParameterTooHigh->throw( error =>
335
"Amount to reduce ($params->{amount}) is higher than original amount ($original)"
336
    ) unless ( $original >= $params->{amount} );
337
    my $reduced =
338
      $self->credits( { credit_type_code => [ 'REFUND' ] } )->total;
339
    Koha::Exceptions::ParameterTooHigh->throw( error =>
340
"Combined reduction ($params->{amount} + $reduced) is higher than original amount ("
341
          . abs($original)
342
          . ")" )
343
      unless ( $original >= ( $params->{amount} + abs($reduced) ) );
344
345
    my $status = { 'REFUND' => 'REFUNDED' };
346
347
    my $reduction;
348
    $self->_result->result_source->schema->txn_do(
349
        sub {
350
351
            # A 'reduction' is a 'credit'
352
            $reduction = Koha::Account::Line->new(
353
                {
354
                    date              => \'NOW()',
355
                    amount            => 0 - $params->{amount},
356
                    credit_type_code  => $params->{reduction_type},
357
                    status            => 'ADDED',
358
                    amountoutstanding => 0 - $params->{amount},
359
                    manager_id        => $params->{staff_id},
360
                    borrowernumber    => $self->borrowernumber,
361
                    interface         => $params->{interface},
362
                    branchcode        => $params->{branch},
363
                }
364
            )->store();
365
366
            my $reduction_offset = Koha::Account::Offset->new(
367
                {
368
                    credit_id => $reduction->accountlines_id,
369
                    type      => uc( $params->{reduction_type} ),
370
                    amount    => $params->{amount}
371
                }
372
            )->store();
373
374
            # Link reduction to charge (and apply as required)
375
            my $debit_outstanding = $self->amountoutstanding;
376
            if ( $debit_outstanding >= $params->{amount} ) {
377
378
                $reduction->apply(
379
                    {
380
                        debits      => [$self],
381
                        offset_type => $params->{reduction_type}
382
                    }
383
                );
384
                $reduction->status('APPLIED')->store();
385
            }
386
            else {
387
388
        # Zero amount offset used to link original 'debit' to reduction 'credit'
389
                my $link_reduction_offset = Koha::Account::Offset->new(
390
                    {
391
                        credit_id => $reduction->accountlines_id,
392
                        debit_id  => $self->accountlines_id,
393
                        type      => $params->{reduction_type},
394
                        amount    => 0
395
                    }
396
                )->store();
397
            }
398
399
            # Update status of original debit
400
            $self->status( $status->{ $params->{reduction_type} } )->store;
401
        }
402
    );
403
404
    return $reduction->discard_changes;
405
}
406
273
=head3 apply
407
=head3 apply
274
408
275
    my $debits = $account->outstanding_debits;
409
    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