Bug 23354: Add a Point Of Sale 'pay' screen
authorMartin Renvoize <martin.renvoize@ptfs-europe.com>
Tue, 23 Jul 2019 16:23:55 +0000 (17:23 +0100)
committerMartin Renvoize <martin.renvoize@ptfs-europe.com>
Mon, 13 Jan 2020 14:03:56 +0000 (14:03 +0000)
This patch adds a new Point Of Sale module to Koha's staff client front
page. The module button leads directly to a 'Pay' page giving the staff
user the ability to record anonymous payments for items that would not
normally require a patron to be registered at the library.

Test plan:
1) Enable `UseCashRegisters` via the system preferences.
2) Ensure your user has the 'manage_cash_registers' permission.
3) Add a cash register for your current branch.
4) Add at least one 'MANUAL_INV' authorized value.
5) Navigate to the new 'POS' pay page via the main menu.
6) Add an item to the 'sale' by clicking 'add' from the right side of
   the screen.
7) Note that said item was added to the table of items this sale on the
   left.
8) At this point you should be able to 'click to edit' the quantity or
   price of the item in the table on the left.
9) Enter an amount greater than the price of the item into the 'amount
   collected from patron' box.
10) Click 'Confirm'
11) Varify that the same change to give modal from the paycollect pages
    appears here.
12) Click 'Confirm'
13) Payment will have been recorded (check the database) and you will be
    back at a fresh 'Pay' page ready for the next transaction.
14) Signoff

Sponsored-by: PTFS Europe
Sponsored-by: Cheshire Libraries Shared Services
Signed-off-by: Kyle M Hall <kyle@bywatersolutions.com>
Signed-off-by: Josef Moravec <josef.moravec@gmail.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>

Koha/Charges/Sales.pm [new file with mode: 0644]
installer/data/mysql/account_offset_types.sql
installer/data/mysql/atomicupdate/bug_23354.perl [new file with mode: 0644]
koha-tmpl/intranet-tmpl/prog/en/includes/pos-menu.inc [new file with mode: 0644]
koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt
koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt [new file with mode: 0644]
pos/pay.pl [new file with mode: 0755]

diff --git a/Koha/Charges/Sales.pm b/Koha/Charges/Sales.pm
new file mode 100644 (file)
index 0000000..27bcc1a
--- /dev/null
@@ -0,0 +1,285 @@
+package Koha::Charges::Sales;
+
+# Copyright 2019 PTFS Europe
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
+
+use Modern::Perl;
+
+use Koha::Account::Lines;
+use Koha::Account::Offsets;
+use Koha::DateUtils qw( dt_from_string );
+use Koha::Exceptions;
+
+=head1 NAME
+
+Koha::Charges::Sale - Module for collecting sales in Koha
+
+=head1 SYNOPSIS
+
+  use Koha::Charges::Sale;
+
+  my $sale =
+    Koha::Charges::Sale->new( { cash_register => $register, staff_id => $staff_id } );
+  $sale->add_item($item);
+  $sale->purchase( { payment_type => 'CASH' } );
+
+=head2 Class methods
+
+=head3 new
+
+  Koha::Charges::Sale->new(
+    {
+        cash_register  => $cash_register,
+        staff_id        => $staff_id,
+        [ payment_type => $payment_type ],
+        [ items        => $items ],
+        [ patron       => $patron ],
+    }
+  );
+
+=cut
+
+sub new {
+    my ( $class, $params ) = @_;
+
+    Koha::Exceptions::MissingParameter->throw(
+        "Missing mandatory parameter: cash_register")
+      unless $params->{cash_register};
+
+    Koha::Exceptions::MissingParameter->throw(
+        "Missing mandatory parameter: staff_id")
+      unless $params->{staff_id};
+
+    Carp::confess("Key 'cash_register' is not a Koha::Cash::Register object!")
+      unless $params->{cash_register}->isa('Koha::Cash::Register');
+
+    return bless( $params, $class );
+}
+
+=head3 payment_type
+
+  my $payment_type = $sale->payment_type( $payment_type );
+
+A getter/setter for this instances associated payment type.
+
+=cut
+
+sub payment_type {
+    my ( $self, $payment_type ) = @_;
+
+    if ($payment_type) {
+        Koha::Exceptions::Account::UnrecognisedType->throw(
+            error => 'Type of payment not recognised' )
+          unless ( exists( $self->_get_valid_payments->{$payment_type} ) );
+
+        $self->{payment_type} = $payment_type;
+    }
+
+    return $self->{payment_type};
+}
+
+=head3 _get_valid_payments
+
+  my $valid_payments = $sale->_get_valid_payments;
+
+A getter which returns a hashref whose keys represent valid payment types.
+
+=cut
+
+sub _get_valid_payments {
+    my $self = shift;
+
+    $self->{valid_payments} //= {
+        map { $_ => 1 } Koha::AuthorisedValues->search(
+            {
+                category   => 'PAYMENT_TYPE',
+                branchcode => $self->{cash_register}->branch
+            }
+        )->get_column('authorised_value')
+    };
+
+    return $self->{valid_payments};
+}
+
+=head3 add_item
+
+  my $item = { price => 0.25, quantity => 1, code => 'COPY' };
+  $sale->add_item( $item );
+
+=cut
+
+sub add_item {
+    my ( $self, $item ) = @_;
+
+    Koha::Exceptions::MissingParameter->throw(
+        "Missing mandatory parameter: code")
+      unless $item->{code};
+
+    Koha::Exceptions::Account::UnrecognisedType->throw(
+        error => 'Type of debit not recognised' )
+      unless ( exists( $self->_get_valid_items->{ $item->{code} } ) );
+
+    Koha::Exceptions::MissingParameter->throw(
+        "Missing mandatory parameter: price")
+      unless $item->{price};
+
+    Koha::Exceptions::MissingParameter->throw(
+        "Missing mandatory parameter: quantity")
+      unless $item->{quantity};
+
+    push @{ $self->{items} }, $item;
+    return $self;
+}
+
+=head3 _get_valid_items
+
+  my $valid_items = $sale->_get_valid_items;
+
+A getter which returns a hashref whose keys represent valid sale items.
+
+=cut
+
+sub _get_valid_items {
+    my $self = shift;
+
+    $self->{valid_items} //= {
+        map { $_ => 1 } Koha::AuthorisedValues->search(
+            {
+                category   => 'MANUAL_INV',
+                branchcode => $self->{cash_register}->branch
+            }
+        )->get_column('authorised_value')
+    };
+
+    return $self->{valid_items};
+}
+
+=head3 purchase
+
+  my $credit_line = $sale->purchase;
+
+=cut
+
+sub purchase {
+    my ( $self, $params ) = @_;
+
+    if ( $params->{payment_type} ) {
+        Koha::Exceptions::Account::UnrecognisedType->throw(
+            error => 'Type of payment not recognised' )
+          unless (
+            exists( $self->_get_valid_payments->{ $params->{payment_type} } ) );
+
+        $self->{payment_type} = $params->{payment_type};
+    }
+
+    Koha::Exceptions::MissingParameter->throw(
+        "Missing mandatory parameter: payment_type")
+      unless $self->{payment_type};
+
+    Koha::Exceptions::NoChanges->throw(
+        "Cannot purchase before calling add_item")
+      unless $self->{items};
+
+    my $schema     = Koha::Database->new->schema;
+    my $dt         = dt_from_string();
+    my $total_owed = 0;
+    my $credit;
+
+    $schema->txn_do(
+        sub {
+
+            # Add accountlines for each item being purchased
+            my $debits;
+            for my $item ( @{ $self->{items} } ) {
+
+                my $amount = $item->{quantity} * $item->{price};
+                $total_owed = $total_owed + $amount;
+
+                # Insert the account line
+                my $debit = Koha::Account::Line->new(
+                    {
+                        amount            => $amount,
+                        accounttype       => $item->{code},
+                        amountoutstanding => 0,
+                        note              => $item->{quantity},
+                        manager_id        => $self->{staff_id},
+                        interface         => 'intranet',
+                        branchcode        => $self->{cash_register}->branch,
+                        date              => $dt
+                    }
+                )->store();
+                push @{$debits}, $debit;
+
+                # Record the account offset
+                my $account_offset = Koha::Account::Offset->new(
+                    {
+                        debit_id => $debit->id,
+                        type     => 'Purchase',
+                        amount   => $amount
+                    }
+                )->store();
+            }
+
+            # Add accountline for payment
+            $credit = Koha::Account::Line->new(
+                {
+                    amount            => 0 - $total_owed,
+                    accounttype       => 'Purchase',
+                    payment_type      => $self->{payment_type},
+                    amountoutstanding => 0,
+                    manager_id        => $self->{staff_id},
+                    interface         => 'intranet',
+                    branchcode        => $self->{cash_register}->branch,
+                    register_id       => $self->{cash_register}->id,
+                    date              => $dt,
+                    note              => "POS SALE"
+                }
+            )->store();
+
+            # Record the account offset
+            my $credit_offset = Koha::Account::Offset->new(
+                {
+                    credit_id => $credit->id,
+                    type      => 'Purchase',
+                    amount    => $credit->amount
+                }
+            )->store();
+
+            # Link payment to debits
+            for my $debit ( @{$debits} ) {
+                Koha::Account::Offset->new(
+                    {
+                        credit_id => $credit->accountlines_id,
+                        debit_id  => $debit->id,
+                        amount    => $debit->amount * -1,
+                        type      => 'Payment',
+                    }
+                )->store();
+            }
+        }
+    );
+
+    return $credit;
+}
+
+=head1 AUTHOR
+
+Martin Renvoize <martin.renvoize@ptfs-europe.com>
+
+=cut
+
+1;
index 5a66f47..5863c23 100644 (file)
@@ -1,6 +1,7 @@
 INSERT INTO account_offset_types ( type ) VALUES
 ('Writeoff'),
 ('Payment'),
+('Purchase'),
 ('Lost Item'),
 ('Processing Fee'),
 ('Manual Credit'),
diff --git a/installer/data/mysql/atomicupdate/bug_23354.perl b/installer/data/mysql/atomicupdate/bug_23354.perl
new file mode 100644 (file)
index 0000000..716cb57
--- /dev/null
@@ -0,0 +1,10 @@
+$DBversion = 'XXX'; # will be replaced by the RM
+if( CheckVersion( $DBversion ) ) {
+
+    $dbh->do(q{
+        INSERT IGNORE INTO account_offset_types ( type ) VALUES ( 'Purchase' );
+    });
+
+    SetVersion( $DBversion );
+    print "Upgrade to $DBversion done (Bug 23354 - Add 'Purchase' account offset type)\n";
+}
diff --git a/koha-tmpl/intranet-tmpl/prog/en/includes/pos-menu.inc b/koha-tmpl/intranet-tmpl/prog/en/includes/pos-menu.inc
new file mode 100644 (file)
index 0000000..e8e51ba
--- /dev/null
@@ -0,0 +1,16 @@
+<div id="navmenu">
+    <div id="navmenulist">
+        [% IF ( CAN_user_cash_management_manage_cash_registers || CAN_user_parameters_manage_auth_values) %]
+        <h5>Administration</h5>
+        <ul>
+            [% IF ( CAN_user_cash_management_manage_cash_registers ) %]
+                <li><a href="/cgi-bin/koha/admin/cash_registers.pl">Cash registers</a></li>
+            [% END %]
+
+            [% IF ( CAN_user_parameters_manage_auth_values ) %]
+                <li><a href="/cgi-bin/koha/admin/authorised_values.pl?searchfield=MANUAL_INV">Purchase items</a></li>
+            [% END %]
+        </ul>
+        [% END %]
+    </div>
+</div>
index 9f4cb52..b3e2910 100644 (file)
                     <div class="col-xs-6">
                         <ul class="biglinks-list">
 
+                            <li>
+                                <a class="icon_general icon_pos" href="/cgi-bin/koha/pos/pay.pl">Point of sale</a>
+                            </li>
+
                             [% IF ( CAN_user_editcatalogue_edit_catalogue || CAN_user_editcatalogue_edit_items ) %]
                             <li>
                                 <a class="icon_general icon_cataloging" href="/cgi-bin/koha/cataloguing/addbooks.pl"><i class="fa fa-tag"></i>Cataloging</a>
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt
new file mode 100644 (file)
index 0000000..33e0fb0
--- /dev/null
@@ -0,0 +1,319 @@
+[% USE raw %]
+[% USE Asset %]
+[% USE Koha %]
+[% USE AuthorisedValues %]
+[% USE Price %]
+[% SET footerjs = 1 %]
+[% INCLUDE 'doc-head-open.inc' %]
+<title>Koha &rsaquo; Payments</title>
+[% INCLUDE 'doc-head-close.inc' %]
+</head>
+
+<body id="payments" class="pos">
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'circ-search.inc' %]
+
+<div id="breadcrumbs"><a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo; Point of sale</div>
+
+<div class="main container-fluid">
+    <div class="row">
+        <div class="col-sm-10 col-sm-push-2">
+
+        [% IF ( error_registers ) %]
+        <div id="error_message" class="dialog alert">
+            You must have at least one cash register associated with this branch before you can record payments.
+        </div>
+        [% ELSE %]
+        <form name="payForm" id="payForm" method="post" action="/cgi-bin/koha/pos/pay.pl">
+            <div class="row">
+
+                <div class="col-sm-6">
+
+                    <fieldset class="rows">
+                        <legend>This sale</legend>
+                        <p>Click to edit item cost or quantities</p>
+                        <table id="sale" class="table_sale">
+                            <thead>
+                                <tr>
+                                    <th>Item</th>
+                                    <th>Cost</th>
+                                    <th>Quantity</th>
+                                    <th>Total</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                            </tbody>
+                            <tfoot>
+                                <tr>
+                                    <td colspan="3">Total payable:</td>
+                                    <td></td>
+                                </tr>
+                            </tfoot>
+                        </table>
+                    </fieldset>
+
+                    <fieldset class="rows">
+                        <legend>Collect payment</legend>
+                        <ol>
+                            <li>
+                                <label for="paid">Amount being paid: </label>
+                                <input name="paid" id="paid" value="[% amountoutstanding | $Price on_editing => 1 %]"/>
+                            </li>
+                            <li>
+                                <label for="collected">Collected from patron: </label>
+                                <input id="collected" value="[% amountoutstanding | $Price on_editing => 1 %]"/>
+                            </li>
+                            <li>
+                                <label>Change to give: </label>
+                                <span id="change">0.00</span>
+                            </li>
+
+                            [% SET payment_types = AuthorisedValues.GetAuthValueDropbox('PAYMENT_TYPE') %]
+                            [% IF payment_types %]
+                            <li>
+                                <label for="payment_type">Payment type: </label>
+                                <select name="payment_type" id="payment_type">
+                                    [% FOREACH pt IN payment_types %]
+                                        <option value="[% pt.authorised_value | html %]">[% pt.lib | html %]</option>
+                                    [% END %]
+                                </select>
+                            </li>
+                            [% END %]
+
+                            [% IF Koha.Preference('UseCashRegisters') %]
+                            <li>
+                                <label for="cash_register">Cash register: </label>
+                                <select name="cash_register" id="cash_register">
+                                    [% FOREACH register IN registers %]
+                                      [% IF register.id == registerid %]
+                                    <option value="[% register.id %]" selected="selected">[% register.name | html %]</option>
+                                      [% ELSE %]
+                                    <option value="[% register.id %]">[% register.name | html %]</option>
+                                      [% END %]
+                                    [% END %]
+                                </select>
+                            </li>
+                            [% END %]
+                        </ol>
+
+                    </fieldset>
+                </div>
+
+                <div class="col-sm-6">
+                    <fieldset class="rows">
+                        <legend>Items for purchase</legend>
+                            [% SET invoice_types = AuthorisedValues.GetAuthValueDropbox('MANUAL_INV') %]
+                            [% IF invoice_types %]
+                            <table id="invoices">
+                            <thead>
+                                <tr>
+                                    <th>Code</th>
+                                    <th>Description</th>
+                                    <th>Cost</th>
+                                    <th>Action</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                            [% FOREACH invoice IN invoice_types %]
+                                <tr>
+                                    <td>[% invoice.authorised_value | html %]</td>
+                                    <td>[% invoice.lib_opac | html %]</td>
+                                    <td>[% invoice.lib | html %]</td>
+                                    <td>
+                                        <button class="add_button" data-invoice-code="[% invoice.lib_opac %]" data-invoice-title="[% invoice.authorised_value | html %]" data-invoice-price="[% invoice.lib | html %]"><i class="fa fa-plus"></i> Add</button>
+                                    </td>
+                                </tr>
+                            [% END %]
+                            </table>
+                            [% ELSE %]
+                            You have no manual invoice types defined
+                            [% END %]
+                    </fieldset>
+                </div>
+
+                <div class="action">
+                    <input type="submit" name="submitbutton" value="Confirm" />
+                    <a class="cancel" href="/cgi-bin/koha/pos/pay.pl">Cancel</a>
+                </div>
+            </div>
+        </form>
+        [% END %]
+    </div>
+
+    <div class="col-sm-2 col-sm-pull-10">
+        <aside>
+            [% INCLUDE 'pos-menu.inc' %]
+        </aside>
+    </div>
+
+</div> <!-- /.row -->
+
+[% MACRO jsinclude BLOCK %]
+    [% Asset.js("js/admin-menu.js") | $raw %]
+    [% INCLUDE 'datatables.inc' %]
+    [% Asset.js("lib/jquery/plugins/jquery.jeditable.mini.js") | $raw %]
+    <script>
+    function fnClickAddRow( table, invoiceTitle, invoicePrice ) {
+      table.fnAddData( [
+        invoiceTitle,
+        invoicePrice,
+        1,
+        null
+         ]
+      );
+    }
+
+    function moneyFormat(textObj) {
+        var newValue = textObj.value;
+        var decAmount = "";
+        var dolAmount = "";
+        var decFlag   = false;
+        var aChar     = "";
+
+        for(i=0; i < newValue.length; i++) {
+            aChar = newValue.substring(i, i+1);
+            if (aChar >= "0" && aChar <= "9") {
+                if(decFlag) {
+                    decAmount = "" + decAmount + aChar;
+                }
+                else {
+                    dolAmount = "" + dolAmount + aChar;
+                }
+            }
+            if (aChar == ".") {
+                if (decFlag) {
+                    dolAmount = "";
+                    break;
+                }
+                decFlag = true;
+            }
+        }
+
+        if (dolAmount == "") {
+            dolAmount = "0";
+        }
+    // Strip leading 0s
+        if (dolAmount.length > 1) {
+            while(dolAmount.length > 1 && dolAmount.substring(0,1) == "0") {
+                dolAmount = dolAmount.substring(1,dolAmount.length);
+            }
+        }
+        if (decAmount.length > 2) {
+            decAmount = decAmount.substring(0,2);
+        }
+    // Pad right side
+        if (decAmount.length == 1) {
+           decAmount = decAmount + "0";
+        }
+        if (decAmount.length == 0) {
+           decAmount = decAmount + "00";
+        }
+
+        textObj.value = dolAmount + "." + decAmount;
+    }
+
+    function updateChangeValues() {
+        var change = $('#change')[0];
+        change.innerHTML = Math.round(($('#collected')[0].value - $('#paid')[0].value) * 100) / 100;
+        if (change.innerHTML <= 0) {
+            change.innerHTML = "0.00";
+        } else {
+            change.value = change.innerHTML;
+            moneyFormat(change);
+            change.innerHTML = change.value;
+        }
+        $('#modal_change').html(change.innerHTML);
+    }
+
+    $(document).ready(function() {
+        var sale_table = $("#sale").dataTable($.extend(true, {}, dataTablesDefaults, {
+            "bPaginate": false,
+            "bFilter": false,
+            "bInfo": false,
+            "bAutoWidth": false,
+            "aoColumnDefs": [{
+                "aTargets": [-2],
+                "bSortable": false,
+                "bSearchable": false,
+            }, {
+                "aTargets": [-1],
+                "mRender": function ( data, type, full ) {
+                    var price = Number.parseFloat(data).toFixed(2);
+                    return '£'+price;
+                }
+            }, {
+                "aTargets": [-2, -3],
+                "sClass" : "editable",
+            }],
+            "aaSorting": [
+                [1, "asc"]
+            ],
+            "fnDrawCallback": function (oSettings) {
+                var local = this;
+                local.$('.editable').editable( function(value, settings) {
+                    var aPos = local.fnGetPosition( this );
+                    local.fnUpdate( value, aPos[0], aPos[1], true, false );
+                    return value;
+                },{
+                    type    : 'text'
+                })
+            },
+            "fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
+                var iTotal = aData[1] * aData[2];
+                this.fnUpdate( iTotal, nRow, 3, false, false );
+            },
+            "fnFooterCallback": function(nFoot, aData, iStart, iEnd, aiDisplay) {
+                var iTotalPrice = 0;
+                for ( var i=0 ; i<aData.length ; i++ )
+                           {
+                                   iTotalPrice += aData[i][3]*1;
+                           }
+
+                iTotalPrice = Number.parseFloat(iTotalPrice).toFixed(2);
+                nFoot.getElementsByTagName('td')[1].innerHTML = iTotalPrice;
+                $('#paid').val(iTotalPrice);
+            }
+        }));
+
+        var items_table = $("#invoices").dataTable($.extend(true,{}, dataTablesDefaults, {
+               "aoColumnDefs": [
+                  { "aTargets": [ -1, -2 ], "bSortable": false, "bSearchable":false },
+               ],
+               "aaSorting": [[ 0, "asc" ]],
+               "paginationType": "full",
+        }));
+
+        $(".add_button").on("click", function(ev) {
+            ev.preventDefault();
+            fnClickAddRow(sale_table, $( this ).data('invoiceTitle'), $( this ).data('invoicePrice') );
+            items_table.fnFilter( '' );
+        });
+
+        $("#paid, #collected").on("change",function() {
+            moneyFormat( this );
+            if (change != undefined) {
+                updateChangeValues();
+            }
+        });
+
+        $("#payForm").submit(function(e){
+            var rows = sale_table.fnGetData();
+            rows.forEach(function (row, index) {
+                var sale = {
+                    code: row[0],
+                    price: row[1],
+                    quantity: row[2]
+                };
+                $('<input>').attr({
+                    type: 'hidden',
+                    name: 'sales',
+                    value: JSON.stringify(sale)
+                }).appendTo('#payForm');
+            });
+            return true;
+        });
+    });
+    </script>
+[% END %]
+
+[% INCLUDE 'intranet-bottom.inc' %]
diff --git a/pos/pay.pl b/pos/pay.pl
new file mode 100755 (executable)
index 0000000..18d968d
--- /dev/null
@@ -0,0 +1,79 @@
+#!/usr/bin/perl
+
+use Modern::Perl;
+
+use CGI;
+use JSON qw( from_json );
+
+use C4::Auth qw/:DEFAULT get_session/;
+use C4::Output;
+use C4::Context;
+
+use Koha::AuthorisedValues;
+use Koha::Cash::Registers;
+use Koha::Charges::Sales;
+use Koha::Database;
+use Koha::Libraries;
+
+my $q         = CGI->new();
+my $sessionID = $q->cookie('CGISESSID');
+my $session   = get_session($sessionID);
+
+my ( $template, $loggedinuser, $cookie, $user_flags ) = get_template_and_user(
+    {
+        template_name   => 'pos/pay.tt',
+        query           => $q,
+        type            => 'intranet',
+        authnotrequired => 0,
+    }
+);
+my $logged_in_user = Koha::Patrons->find($loggedinuser) or die "Not logged in";
+
+my $library_id = C4::Context->userenv->{'branch'};
+my $registerid = $q->param('registerid');
+my $registers  = Koha::Cash::Registers->search(
+    { branch   => $library_id, archived => 0 },
+    { order_by => { '-asc' => 'name' } }
+);
+
+if ( !$registers->count ) {
+    $template->param( error_registers => 1 );
+}
+else {
+    if ( !$registerid ) {
+        my $default_register = Koha::Cash::Registers->find(
+            { branch => $library_id, branch_default => 1 } );
+        $registerid = $default_register->id if $default_register;
+    }
+    $registerid = $registers->next->id if !$registerid;
+
+    $template->param(
+        registerid => $registerid,
+        registers  => $registers,
+    );
+}
+
+my $total_paid = $q->param('paid');
+if ( $total_paid and $total_paid ne '0.00' ) {
+    warn "total_paid: $total_paid\n";
+    my $cash_register = Koha::Cash::Registers->find( { id => $registerid } );
+    my $payment_type  = $q->param('payment_type');
+    my $sale          = Koha::Charges::Sales->new(
+        {
+            cash_register => $cash_register,
+            staff_id      => $logged_in_user->id
+        }
+    );
+
+    my @sales = $q->multi_param('sales');
+    for my $item (@sales) {
+        $item = from_json $item;
+        $sale->add_item($item);
+    }
+
+    $sale->purchase( { payment_type => $payment_type } );
+}
+
+output_html_with_http_headers( $q, $cookie, $template->output );
+
+1;