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

(-)a/Koha/Account/Line.pm (+152 lines)
Lines 187-192 sub void { Link Here
187
187
188
}
188
}
189
189
190
=head3 reduce
191
192
  $charge_accountline->reduce({
193
      reduction_type => $reduction_type
194
  });
195
196
Used to 'reduce' a charge/debit by adding a credit to offset against the amount outstanding.
197
198
May be used to apply a discount whilst retaining the original debit amounts or to apply a full or partial refund for example when a lost item is found and returned.
199
200
It will immediately be applied to the given debit unless the debit has already been paid, in which case a 'zero' offset will be added to maintain a link to the debit but the outstanding credit will be left so it may be applied to other debts.
201
202
Reduction type may be one of:
203
204
* Discount
205
* Refund
206
207
Returns the reduction accountline (which will be a credit)
208
209
=cut
210
211
sub reduce {
212
    my ( $self, $params ) = @_;
213
214
    # Make sure it is a charge we are reducing
215
    return unless $self->amount > 0;
216
217
    my $reduction;
218
    $self->_result->result_source->schema->txn_do(
219
        sub {
220
221
            # A 'reduction' is a 'credit'
222
            $reduction = Koha::Account::Line->new(
223
                {
224
                    date              => \'NOW()',
225
                    amount            => 0 - $params->{amount},
226
                    accounttype       => $params->{reduction_type},
227
                    payment_type      => $params->{payment_type},
228
                    amountoutstanding => 0 - $params->{amount},
229
                    manager_id        => $params->{staff_id},
230
                    borrowernumber    => $params->{patron_id},
231
                    interface         => 'intranet',
232
                    branchcode        => $self->branch,
233
                }
234
            )->store();
235
236
            my $create_reduction_offset = Koha::Account::Offset->new(
237
                {
238
                    credit_id => $reduction->accountlines_id,
239
                    type      => uc( $params->{reduction_type} ),
240
                    amount    => $params->{amount}
241
                }
242
            )->store();
243
244
            # Link reduction to debit (and apply as required)
245
            my $debit_outstanding = $self->amountoutstanding;
246
            if ( $debit_outstanding >= $params->{amount} ) {
247
248
                my $credit_outstanding = $reduction->apply(
249
                    { debits => [$self], offset_type => uc($params->{reduction_type}) } );
250
            }
251
            else {
252
253
                # Zero amount offset used to link original 'debit' to reduction 'credit'
254
                my $link_reduction_offset = Koha::Account::Offset->new(
255
                    {
256
                        credit_id => $reduction->accountlines_id,
257
                        debit_id  => $self->accountlines_id,
258
                        type      => uc($params->{reduction_type}),
259
                        amount    => 0
260
                    }
261
                )->store();
262
            }
263
        }
264
    );
265
266
    return $reduction;
267
}
268
269
=head3 payout
270
271
  $credit_accountline->payout(
272
    {
273
        payout_type => $payout_type,
274
        register_id => $register_id,
275
        amount      => $amount
276
    }
277
  );
278
279
Used to 'pay out' a credit to a user.
280
281
Payout type may be one of any existing payment types
282
283
=cut
284
285
sub payout {
286
    my ( $self, $params ) = @_;
287
288
    # Make sure it is a credit we are paying out
289
    return unless $self->amount < 0;
290
291
    # Make sure there is outstanding credit to pay out
292
    my $amount = $params->{amount} ? $params->{amount} : $self->amountoutstanding;
293
    return unless $self->amountoutstanding >= $amount;
294
295
    # Make sure we record the cash register for cash transactions
296
    return if ( $params->{payout_type} eq 'CASH' && !defined($params->{cash_register}));
297
298
    my $payout;
299
    $self->_result->result_source->schema->txn_do(
300
        sub {
301
302
            # A 'payout' is a 'debit'
303
            $payout = Koha::Account::Line->new(
304
                {
305
                    date              => \'NOW()',
306
                    amount            => 0 - $amount,
307
                    accounttype       => 'Payout',
308
                    payment_type      => $params->{payout_type},
309
                    amountoutstanding => 0,
310
                    manager_id        => $params->{staff_id},
311
                    borrowernumber    => $params->{patron_id},
312
                    interface         => 'intranet',
313
                    branchcode        => $self->branch,
314
                    register_id       => $params->{cash_register},
315
                    note              => $params->{quantity}
316
                }
317
            )->store();
318
319
            my $create_payout_offset = Koha::Account::Offset->new(
320
                {
321
                    debit_id => $payout->accountlines_id,
322
                    type     => 'PAYOUT',
323
                    amount   => 0 - $amount
324
                }
325
            )->store();
326
327
            my $payout_reduction_offset = Koha::Account::Offset->new(
328
                {
329
                    debit_id  => $payout->accountlines_id,
330
                    credit_id => $self->accountlines_id,
331
                    type      => 'PAYOUT',
332
                    amount    => 0 - $amount
333
                }
334
            )->store();
335
        }
336
    );
337
338
    return $payout;
339
}
340
341
190
=head3 apply
342
=head3 apply
191
343
192
    my $debits = $account->outstanding_debits;
344
    my $debits = $account->outstanding_debits;
(-)a/Koha/Cash/Register.pm (-1 / +93 lines)
Lines 220-226 sub add_cashup { Link Here
220
    my $rs = $self->_result->add_to_cash_register_actions(
220
    my $rs = $self->_result->add_to_cash_register_actions(
221
        {
221
        {
222
            code       => 'CASHUP',
222
            code       => 'CASHUP',
223
            manager_id => $params->{user_id},
223
            manager_id => $params->{staff_id},
224
            amount     => $params->{amount}
224
            amount     => $params->{amount}
225
        }
225
        }
226
    )->discard_changes;
226
    )->discard_changes;
Lines 228-233 sub add_cashup { Link Here
228
    return Koha::Cash::Register::Action->_new_from_dbic($rs);
228
    return Koha::Cash::Register::Action->_new_from_dbic($rs);
229
}
229
}
230
230
231
=head3 issue_refund
232
233
Add a refund to the till, returns the added 'payout' accountline.
234
235
NOTE: How do VOID and REFUND interact?
236
237
NOTE: Should this really live inside Koha::Account::Line (and only be applicable to debit lines)
238
NOTE: This should also probably check for corresponding payment lines before allowing a 'payout' or be split into two routines.
239
240
=cut
241
242
sub issue_refund {
243
    my ( $self, $params ) = @_;
244
245
    my $schema = Koha::Database->new->schema;
246
247
    my $payout;
248
    $schema->txn_do(
249
        sub {
250
251
            # A 'refund' is a 'credit'
252
            my $refund = Koha::Account::Line->new(
253
                {
254
                    date              => \'NOW()',
255
                    amount            => 0 - $params->{amount},
256
                    accounttype       => 'Refund',
257
                    payment_type      => $params->{payment_type},
258
                    amountoutstanding => 0,
259
                    manager_id        => $params->{staff_id},
260
                    borrowernumber    => $params->{patron_id},
261
                    interface         => 'intranet',
262
                    branchcode        => $self->branch,
263
                }
264
            )->store();
265
266
            my $create_refund_offset = Koha::Account::Offset->new(
267
                {
268
                    credit_id => $refund->accountlines_id,
269
                    type      => 'REFUND',
270
                    amount    => $params->{amount}
271
                }
272
            )->store();
273
274
            # Zero amount offset used to link original 'debit' to refund 'credit'
275
            my $link_refund_offset = Koha::Account::Offset->new(
276
                {
277
                    credit_id => $refund->accountlines_id,
278
                    debit_id  => $params->{accountline}->accountlines_id,
279
                    type      => 'LINK',
280
                    amount    => 0
281
                }
282
            )->store();
283
284
            # A 'payout' is a 'debit'
285
            $payout = Koha::Account::Line->new(
286
                {
287
                    date              => \'NOW()',
288
                    amount            => $params->{amount},
289
                    accounttype       => 'Payout',
290
                    payment_type      => $params->{payment_type},
291
                    amountoutstanding => 0,
292
                    manager_id        => $params->{staff_id},
293
                    borrowernumber    => $params->{patron_id},
294
                    interface         => 'intranet',
295
                    branchcode        => $self->branch,
296
                    register_id       => $self->id,
297
                    note              => $params->{quantity}
298
                }
299
            )->store();
300
301
            my $create_payout_offset = Koha::Account::Offset->new(
302
                {
303
                    debit_id  => $payout->accountlines_id,
304
                    type      => 'PAYOUT',
305
                    amount    => $params->{amount}
306
                }
307
            )->store();
308
309
            my $payout_refund_offset = Koha::Account::Offset->new(
310
                {
311
                    debit_id  => $payout->accountlines_id,
312
                    credit_id => $refund->accountlines_id,
313
                    type      => 'PAYOUT',
314
                    amount    => 0 - $params->{amount}
315
                }
316
            )->store();
317
        }
318
    );
319
320
    return $payout;
321
}
322
231
=head2 Internal methods
323
=head2 Internal methods
232
324
233
=cut
325
=cut
(-)a/installer/data/mysql/account_offset_types.sql (+3 lines)
Lines 6-11 INSERT INTO account_offset_types ( type ) VALUES Link Here
6
('Manual Credit'),
6
('Manual Credit'),
7
('Manual Debit'),
7
('Manual Debit'),
8
('Reverse Payment'),
8
('Reverse Payment'),
9
('REFUND'),
10
('LINK'),
11
('PAYOUT'),
9
('Forgiven'),
12
('Forgiven'),
10
('Dropbox'),
13
('Dropbox'),
11
('Account Fee'),
14
('Account Fee'),
(-)a/installer/data/mysql/atomicupdate/bug_23442.perl (+22 lines)
Line 0 Link Here
1
$DBversion = 'XXX';    # will be replaced by the RM
2
if ( CheckVersion($DBversion) ) {
3
4
    $dbh->do(qq{
5
        INSERT IGNORE permissions (module_bit, code, description)
6
        VALUES
7
        (25, 'refund_cash_registers', 'Perform refund actions from cash registers')
8
    });
9
10
    $dbh->do(q{
11
        INSERT IGNORE INTO account_offset_types ( type ) VALUES ( 'REFUND' );
12
    });
13
    $dbh->do(q{
14
        INSERT IGNORE INTO account_offset_types ( type ) VALUES ( 'LINK' );
15
    });
16
    $dbh->do(q{
17
        INSERT IGNORE INTO account_offset_types ( type ) VALUES ( 'PAYOUT' );
18
    });
19
20
    SetVersion($DBversion);
21
    print "Upgrade to $DBversion done (Bug 23442 - Add a refund option to the point of sale system)\n";
22
}
(-)a/koha-tmpl/intranet-tmpl/prog/en/includes/pos-menu.inc (-1 / +1 lines)
Lines 1-6 Link Here
1
<div id="navmenu">
1
<div id="navmenu">
2
    <div id="navmenulist">
2
    <div id="navmenulist">
3
        [% IF ( CAN_user_cash_management_cashup_cash_registers ) %]
3
        [% IF ( CAN_user_cash_management_cashup_cash_registers || CAN_user_cash_management_refund_cash_registers ) %]
4
        <h5>Point of sale</h5>
4
        <h5>Point of sale</h5>
5
        <ul>
5
        <ul>
6
            <li><a href="/cgi-bin/koha/pos/register.pl">Register details</a></li>
6
            <li><a href="/cgi-bin/koha/pos/register.pl">Register details</a></li>
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/register.tt (-2 / +94 lines)
Lines 27-35 Link Here
27
                You must have at least one cash register associated with this branch before you can record payments.
27
                You must have at least one cash register associated with this branch before you can record payments.
28
            </div>
28
            </div>
29
            [% ELSE %]
29
            [% ELSE %]
30
            [% IF ( CAN_user_cash_management_cashup_cash_registers ) %]
30
            <div id="toolbar" class="btn-toolbar">
31
            <div id="toolbar" class="btn-toolbar">
31
                <a id="cashup" href="/cgi-bin/koha/pos/register.pl?op=cashup" class="btn btn-default"><i class="fa fa-money"></i> Record cashup</a>
32
                <a id="cashup" href="/cgi-bin/koha/pos/register.pl?op=cashup" class="btn btn-default"><i class="fa fa-money"></i> Record cashup</a>
32
            </div>
33
            </div>
34
            [% END %]
33
35
34
            <h1>Register transaction details for [% register.name | html %]</h1>
36
            <h1>Register transaction details for [% register.name | html %]</h1>
35
37
Lines 57-62 Link Here
57
                    <th>
59
                    <th>
58
                        Transaction
60
                        Transaction
59
                    </th>
61
                    </th>
62
                    <th>
63
                        Account type
64
                    </th>
60
                    <th>
65
                    <th>
61
                        Description of charges
66
                        Description of charges
62
                    </th>
67
                    </th>
Lines 74-94 Link Here
74
                    [% FOREACH accountline IN accountlines %]
79
                    [% FOREACH accountline IN accountlines %]
75
                        [% IF accountline.is_credit %]
80
                        [% IF accountline.is_credit %]
76
                            [% FOREACH credit IN accountline.credit_offsets %]
81
                            [% FOREACH credit IN accountline.credit_offsets %]
82
                            [% IF credit.debit %]
77
                            <tr>
83
                            <tr>
78
                                <td>[% accountline.accountlines_id %]</td>
84
                                <td>[% accountline.accountlines_id %]</td>
79
                                <td>{ "type": "credit", "description": "[%- PROCESS account_type_description account=accountline -%] ([% accountline.payment_type | html %])", "amount": "[% accountline.amount * -1 | $Price %]" }</td>
85
                                <td>{ "type": "credit", "description": "[%- PROCESS account_type_description account=accountline -%] ([% accountline.payment_type | html %])", "amount": "[% accountline.amount * -1 | $Price %]" }</td>
80
                                <td>[%- PROCESS account_type_description account=credit.debit -%]</td>
86
                                <td>[%- PROCESS account_type_description account=credit.debit -%]</td>
87
                                <td>[%- IF credit.debit.description %][% credit.debit.description | html %][% END %]
88
        &nbsp;[% IF ( credit.debit.itemnumber ) %]<a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% credit.debit.item.biblionumber | uri %]&amp;itemnumber=[% credit.debit.itemnumber | uri %]">[% credit.debit.item.biblio.title | html %]</a>[%- END -%]</td>
81
                                <td>[% credit.debit.amount | $Price %]</td>
89
                                <td>[% credit.debit.amount | $Price %]</td>
82
                                <td></td>
90
                                <td></td>
91
                                <td>
92
                                    [% IF ( CAN_user_cash_management_refund_cash_registers ) %]
93
                                    <button type="button" data-toggle="modal" data-target="#issueRefundModal" data-item="[%- PROCESS account_type_description account=credit.debit -%]" data-accountline="[% credit.debit.accountlines_id | html %]" data-amount="[% credit.debit.amount | $Price %]" data-quantity="[% credit.debit.note | html %]"><i class="fa fa-plus"></i> Issue refund</button>
94
                                    [% END %]
95
                                </td>
96
                            </tr>
97
                            [% END %]
98
                            [% END %]
99
                        [% ELSE %]
100
                            [% FOREACH debit IN accountline.debit_offsets %]
101
                            [% IF debit.credit %]
102
                            <tr>
103
                                <td>[% accountline.accountlines_id %]</td>
104
                                <td>{ "type": "debit", "description": "[%- PROCESS account_type_description account=accountline -%] ([% accountline.payment_type | html %])", "amount": "[% accountline.amount * -1 | $Price %]" }</td>
105
                                <td>[%- PROCESS account_type_description account=debit.credit -%]</td>
106
                                <td>[%- IF debit.credit.description %][% debit.credit.description | html %][% END %]
107
        &nbsp;[% IF ( debit.credit.itemnumber ) %]<a href="/cgi-bin/koha/catalogue/moredetail.pl?biblionumber=[% debit.credit.item.biblionumber | uri %]&amp;itemnumber=[% debit.credit.itemnumber | uri %]">[% debit.credit.item.biblio.title | html %]</a>[% END %]</td>
108
                                <td>[% debit.credit.amount | $Price %]</td>
109
                                <td></td>
83
                                <td></td>
110
                                <td></td>
84
                            </tr>
111
                            </tr>
85
                            [% END %]
112
                            [% END %]
113
                            [% END %]
86
                        [% END %]
114
                        [% END %]
87
                    [% END %]
115
                    [% END %]
88
                </tbody>
116
                </tbody>
89
                <tfoot>
117
                <tfoot>
90
                    <tr>
118
                    <tr>
91
                        <td colspan="4">Total income: </td>
119
                        <td colspan="5">Total income: </td>
92
                        <td>[% accountlines.total * -1 | $Price %]</td>
120
                        <td>[% accountlines.total * -1 | $Price %]</td>
93
                        <td></td>
121
                        <td></td>
94
                    </tr>
122
                    </tr>
Lines 104-109 Link Here
104
        </div>
132
        </div>
105
    </div><!-- /.row -->
133
    </div><!-- /.row -->
106
134
135
    <!-- Issue refund modal -->
136
    <div class="modal" id="issueRefundModal" tabindex="-1" role="dialog" aria-labelledby="issueRefundLabel">
137
        <form id="refund_form" method="post" enctype="multipart/form-data" class="validated">
138
            <input type="hidden" name="accountline" value="" id="refundline">
139
            <div class="modal-dialog" role="document">
140
                <div class="modal-content">
141
                    <div class="modal-header">
142
                        <button type="button" class="closebtn" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
143
                        <h4 class="modal-title" id="issueRefundLabel">Issue refund from <em>[% register.name | html %]</em></h4>
144
                    </div>
145
                    <div class="modal-body">
146
                        <fieldset class="rows">
147
                            <ol>
148
                                <li>
149
                                    <span id="item" class="label">Item: </span><span></span>
150
                                </li>
151
                                <li>
152
                                    <label class="required" for="amount">Amount:</label>
153
                                    <input type="text" id="amount" name="amount" value="[% refund.amount | html %]" required="required">
154
                                    <span class="required">Required</span>
155
                                </li>
156
                                <li>
157
                                    <label class="required" for="quantity">Quantity:</label>
158
                                    <input type="text" id="quantity" name="quantity" value="[% refund.quantity | html %]" required="required">
159
                                    <span class="required">Required</span>
160
                                </li>
161
                                [% SET payment_types = AuthorisedValues.GetAuthValueDropbox('PAYMENT_TYPE') %]
162
                                [% IF payment_types %]
163
                                <li>
164
                                    <label for="transaction_type">Transaction type: </label>
165
                                    <select name="transaction_type" id="transaction_type">
166
                                        [% FOREACH pt IN payment_types %]
167
                                            <option value="[% pt.authorised_value | html %]">[% pt.lib | html %]</option>
168
                                        [% END %]
169
                                    </select>
170
                                </li>
171
                                [% END %]
172
                            </ol>
173
                        </fieldset> <!-- /.rows -->
174
                    </div> <!-- /.modal-body -->
175
                    <div class="modal-footer">
176
                        <input type="hidden" name="registerid" value="[% register.id | html %]">
177
                        <input type="hidden" name="op" value="refund">
178
                        <button type="submit" class="btn btn-default">Confirm</button>
179
                        <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
180
                    </div> <!-- /.modal-footer -->
181
                </div> <!-- /.modal-content -->
182
            </div> <!-- /.modal-dialog -->
183
        </form> <!-- /#refund_form -->
184
    </div> <!-- /#issueRefundModal -->
185
107
[% MACRO jsinclude BLOCK %]
186
[% MACRO jsinclude BLOCK %]
108
    [% INCLUDE 'datatables.inc' %]
187
    [% INCLUDE 'datatables.inc' %]
109
    [% Asset.js("lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js") | $raw %]
188
    [% Asset.js("lib/jquery/plugins/rowGroup/dataTables.rowGroup.min.js") | $raw %]
Lines 119-125 Link Here
119
                startRender: function ( rows, group ) {
198
                startRender: function ( rows, group ) {
120
                    var details = JSON.parse(rows.data().pluck(1).pop());
199
                    var details = JSON.parse(rows.data().pluck(1).pop());
121
                    return $('<tr class="'+details.type+'"/>')
200
                    return $('<tr class="'+details.type+'"/>')
122
                        .append( '<td colspan="2">'+group+' '+details.description+'</td>' )
201
                        .append( '<td colspan="3">'+group+' '+details.description+'</td>' )
123
                        .append( '<td>'+details.amount+'</td>' )
202
                        .append( '<td>'+details.amount+'</td>' )
124
                        .append( '<td><button class="printReceipt" data-accountline="'+group+'"><i class="fa fa-print"></i> Print receipt</button></td>');
203
                        .append( '<td><button class="printReceipt" data-accountline="'+group+'"><i class="fa fa-print"></i> Print receipt</button></td>');
125
                },
204
                },
Lines 127-132 Link Here
127
            }
206
            }
128
        }));
207
        }));
129
208
209
        $("#issueRefundModal").on("shown.bs.modal", function(e){
210
           var button = $(e.relatedTarget);
211
           var item = button.data('item');
212
           $("#item + span").replaceWith(item);
213
           var accountline = button.data('accountline');
214
           $('#refundline').val(accountline);
215
           var amount = button.data('amount');
216
           $("#amount").val(amount);
217
           var quantity = button.data('quantity');
218
           $("#quantity").val(quantity);
219
           $("#amount, #quantity, #transaction_type").focus();
220
        });
221
130
        $(".printReceipt").click(function() {
222
        $(".printReceipt").click(function() {
131
            var accountlines_id = $(this).data('accountline');
223
            var accountlines_id = $(this).data('accountline');
132
            var win = window.open('/cgi-bin/koha/pos/printreceipt.pl?action=print&accountlines_id=' + accountlines_id, '_blank');
224
            var win = window.open('/cgi-bin/koha/pos/printreceipt.pl?action=print&accountlines_id=' + accountlines_id, '_blank');
(-)a/pos/register.pl (-2 / +21 lines)
Lines 24-29 use C4::Auth; Link Here
24
use C4::Output;
24
use C4::Output;
25
use C4::Context;
25
use C4::Context;
26
26
27
use Koha::Account::Lines;
27
use Koha::Cash::Registers;
28
use Koha::Cash::Registers;
28
use Koha::Database;
29
use Koha::Database;
29
30
Lines 74-81 my $op = $q->param('op') // ''; Link Here
74
if ( $op eq 'cashup' ) {
75
if ( $op eq 'cashup' ) {
75
    $cash_register->add_cashup(
76
    $cash_register->add_cashup(
76
        {
77
        {
77
            user_id => $logged_in_user->id,
78
            staff_id => $logged_in_user->id,
78
            amount  => $cash_register->outstanding_accountlines->total
79
            amount   => $cash_register->outstanding_accountlines->total
80
        }
81
    );
82
}
83
elsif ( $op eq 'refund' ) {
84
    my $amount           = $q->param('amount');
85
    my $quantity         = $q->param('quantity');
86
    my $accountline_id   = $q->param('accountline');
87
    my $transaction_type = $q->param('transaction_type');
88
89
    my $accountline = Koha::Account::Lines->find($accountline_id);
90
    $cash_register->issue_refund(
91
        {
92
            staff_id     => $logged_in_user->id,
93
            patron_id    => $accountline->borrowernumber,
94
            amount       => $amount,
95
            payment_type => $transaction_type,
96
            quantity     => $quantity,
97
            accountline  => $accountline
79
        }
98
        }
80
    );
99
    );
81
}
100
}
(-)a/pos/registers.pl (-3 / +2 lines)
Lines 59-66 if ( $op eq 'cashup' ) { Link Here
59
    for my $register ( $registers->as_list ) {
59
    for my $register ( $registers->as_list ) {
60
        $register->add_cashup(
60
        $register->add_cashup(
61
            {
61
            {
62
                user_id => $logged_in_user->id,
62
                staff_id => $logged_in_user->id,
63
                amount  => $register->outstanding_accountlines->total
63
                amount   => $register->outstanding_accountlines->total
64
            }
64
            }
65
        );
65
        );
66
    }
66
    }
67
- 

Return to bug 23442