From 3e6ef8614a5c7eaf47a903b68518c404cc657091 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Tue, 23 Jul 2019 17:23:55 +0100 Subject: [PATCH] Bug 23354: Add a Point Of Sale 'pay' screen 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 Signed-off-by: Josef Moravec Signed-off-by: Martin Renvoize --- Koha/Charges/Sales.pm | 285 ++++++++++++++++ installer/data/mysql/account_offset_types.sql | 1 + .../data/mysql/atomicupdate/bug_23354.perl | 10 + .../prog/en/includes/pos-menu.inc | 16 + .../prog/en/modules/intranet-main.tt | 4 + .../intranet-tmpl/prog/en/modules/pos/pay.tt | 319 ++++++++++++++++++ pos/pay.pl | 79 +++++ 7 files changed, 714 insertions(+) create mode 100644 Koha/Charges/Sales.pm create mode 100644 installer/data/mysql/atomicupdate/bug_23354.perl create mode 100644 koha-tmpl/intranet-tmpl/prog/en/includes/pos-menu.inc create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/pos/pay.tt create mode 100755 pos/pay.pl diff --git a/Koha/Charges/Sales.pm b/Koha/Charges/Sales.pm new file mode 100644 index 0000000000..27bcc1a399 --- /dev/null +++ b/Koha/Charges/Sales.pm @@ -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 . + +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 + +=cut + +1; diff --git a/installer/data/mysql/account_offset_types.sql b/installer/data/mysql/account_offset_types.sql index 5a66f476e8..5863c2341c 100644 --- a/installer/data/mysql/account_offset_types.sql +++ b/installer/data/mysql/account_offset_types.sql @@ -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 index 0000000000..716cb57376 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug_23354.perl @@ -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 index 0000000000..e8e51bab53 --- /dev/null +++ b/koha-tmpl/intranet-tmpl/prog/en/includes/pos-menu.inc @@ -0,0 +1,16 @@ + diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt index 9f4cb52439..b3e2910c6e 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/intranet-main.tt @@ -80,6 +80,10 @@