Browse Source
This patch creates a Koha::Cash::Register::Cashup(s) class pair which subclass Koha::Cash::Register::Action(s) and moves the cashup specific code into these new classes to improve code separation. We then introduce API routes based on these classes to allow fetching a list of cashups associated to a cash register and a full cashup with emeddable summary for individual cashups. Test plan 1/ Run the updated unit tests. t/db_dependent/Koha/Cash/Register/Action.t t/db_dependent/Koha/Cash/Register/Cashup.t 2/ Run the incuded api tests. t/db_dependent/api/v1/cashups.t Signed-off-by: David Nind <david@davidnind.com> Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io> Signed-off-by: Jonathan Druart <jonathan.druart@bugs.koha-community.org>21.05.x
17 changed files with 1159 additions and 380 deletions
@ -0,0 +1,208 @@ |
|||
package Koha::Cash::Register::Cashup; |
|||
|
|||
# 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 Carp; |
|||
|
|||
use Koha::Database; |
|||
|
|||
use base qw(Koha::Cash::Register::Action); |
|||
|
|||
=head1 NAME |
|||
|
|||
Koha::Cash::Register::Actions - Koha Cash Register Action Object set class |
|||
|
|||
=head1 API |
|||
|
|||
=head2 Class methods |
|||
|
|||
=head3 search |
|||
|
|||
my $cashup = Koha::Cash::Register::Actions::Cashup->search( $where, $attr ); |
|||
|
|||
Returns a list of cash register cashup. |
|||
|
|||
=cut |
|||
|
|||
sub search { |
|||
my ( $self, $where, $attr ) = @_; |
|||
|
|||
$where->{code} = 'CASHUP'; |
|||
|
|||
unless ( exists $attr->{order_by} ) { |
|||
$attr->{order_by} = |
|||
[ { '-asc' => 'register_id' }, { '-desc' => 'timestamp' } ]; |
|||
} |
|||
|
|||
return $self->SUPER::search( $where, $attr ); |
|||
} |
|||
|
|||
=head3 summary |
|||
|
|||
my $summary = $cashup->summary; |
|||
|
|||
Return a hashref containing a summary of transactions that make up this cashup. |
|||
|
|||
=cut |
|||
|
|||
sub summary { |
|||
my ($self) = @_; |
|||
my $summary; |
|||
my $prior_cashup = Koha::Cash::Register::Cashups->search( |
|||
{ |
|||
'timestamp' => { '<' => $self->timestamp }, |
|||
register_id => $self->register_id |
|||
}, |
|||
{ |
|||
order_by => { '-desc' => [ 'timestamp', 'id' ] }, |
|||
rows => 1 |
|||
} |
|||
); |
|||
|
|||
my $previous = $prior_cashup->single; |
|||
|
|||
my $conditions = |
|||
$previous |
|||
? { |
|||
'date' => { |
|||
'-between' => |
|||
[ $previous->_result->get_column('timestamp'), $self->timestamp ] |
|||
} |
|||
} |
|||
: { 'date' => { '<' => $self->timestamp } }; |
|||
|
|||
my $payout_transactions = $self->register->accountlines->search( |
|||
{ %{$conditions}, credit_type_code => undef }, |
|||
); |
|||
my $income_transactions = $self->register->accountlines->search( |
|||
{ %{$conditions}, debit_type_code => undef }, |
|||
); |
|||
|
|||
my $income_summary = Koha::Account::Offsets->search( |
|||
{ |
|||
'me.credit_id' => { |
|||
'-in' => $income_transactions->_resultset->get_column( |
|||
'accountlines_id')->as_query |
|||
}, |
|||
'me.debit_id' => { '!=' => undef } |
|||
}, |
|||
{ |
|||
join => { 'debit' => 'debit_type_code' }, |
|||
group_by => |
|||
[ 'debit.debit_type_code', 'debit_type_code.description' ], |
|||
'select' => [ |
|||
{ sum => 'me.amount' }, 'debit.debit_type_code', |
|||
'debit_type_code.description' |
|||
], |
|||
'as' => [ 'total', 'debit_type_code', 'debit_description' ], |
|||
} |
|||
); |
|||
|
|||
my $payout_summary = Koha::Account::Offsets->search( |
|||
{ |
|||
'me.debit_id' => { |
|||
'-in' => $payout_transactions->_resultset->get_column( |
|||
'accountlines_id')->as_query |
|||
}, |
|||
'me.credit_id' => { '!=' => undef } |
|||
}, |
|||
{ |
|||
join => { 'credit' => 'credit_type_code' }, |
|||
group_by => |
|||
[ 'credit.credit_type_code', 'credit_type_code.description' ], |
|||
'select' => [ |
|||
{ sum => 'me.amount' }, 'credit.credit_type_code', |
|||
'credit_type_code.description' |
|||
], |
|||
'as' => [ 'total', 'credit_type_code', 'credit_description' ], |
|||
} |
|||
); |
|||
|
|||
my @income = map { |
|||
{ |
|||
total => $_->get_column('total') * -1, |
|||
debit_type_code => $_->get_column('debit_type_code'), |
|||
debit_type => { description => $_->get_column('debit_description') } |
|||
} |
|||
} $income_summary->as_list; |
|||
my @payout = map { |
|||
{ |
|||
total => $_->get_column('total') * -1, |
|||
credit_type_code => $_->get_column('credit_type_code'), |
|||
credit_type => |
|||
{ description => $_->get_column('credit_description') } |
|||
} |
|||
} $payout_summary->as_list; |
|||
|
|||
my $income_total = $income_transactions->total; |
|||
my $payout_total = $payout_transactions->total; |
|||
my $total = ( $income_total + $payout_total ); |
|||
|
|||
my $payment_types = Koha::AuthorisedValues->search( |
|||
{ |
|||
category => 'PAYMENT_TYPE' |
|||
}, |
|||
{ |
|||
order_by => ['lib'], |
|||
} |
|||
); |
|||
|
|||
my @total_grouped; |
|||
for my $type ( $payment_types->as_list ) { |
|||
my $typed_income = $income_transactions->total( { payment_type => $type->authorised_value } ); |
|||
my $typed_payout = $payout_transactions->total( { payment_type => $type->authorised_value } ); |
|||
my $typed_total = ( $typed_income + $typed_payout ); |
|||
push @total_grouped, { payment_type => $type->lib, total => $typed_total }; |
|||
} |
|||
|
|||
$summary = { |
|||
from_date => $previous ? $previous->timestamp : undef, |
|||
to_date => $self->timestamp, |
|||
income_grouped => \@income, |
|||
income_total => abs($income_total), |
|||
payout_grouped => \@payout, |
|||
payout_total => abs($payout_total), |
|||
total => $total * -1, |
|||
total_grouped => \@total_grouped |
|||
}; |
|||
|
|||
return $summary; |
|||
} |
|||
|
|||
=head3 to_api_mapping |
|||
|
|||
This method returns the mapping for representing a Koha::Cash::Regiser::Cashup object |
|||
on the API. |
|||
|
|||
=cut |
|||
|
|||
sub to_api_mapping { |
|||
return { |
|||
id => 'cashup_id', |
|||
register_id => 'cash_register_id', |
|||
code => undef |
|||
}; |
|||
} |
|||
|
|||
1; |
|||
|
|||
=head1 AUTHORS |
|||
|
|||
Martin Renvoize <martin.renvoize@ptfs-europe.com> |
|||
|
|||
=cut |
@ -0,0 +1,63 @@ |
|||
package Koha::Cash::Register::Cashups; |
|||
|
|||
# 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 Carp; |
|||
|
|||
use Koha::Database; |
|||
use Koha::Cash::Register::Cashup; |
|||
|
|||
use base qw(Koha::Cash::Register::Actions); |
|||
|
|||
=head1 NAME |
|||
|
|||
Koha::Cash::Register::Actions - Koha Cash Register Action Object set class |
|||
|
|||
=head1 API |
|||
|
|||
=head2 Class methods |
|||
|
|||
=head3 search |
|||
|
|||
my $cashups = Koha::Cash::Register::Cashups->search( $where, $attr ); |
|||
|
|||
Returns a list of cash register cashups. |
|||
|
|||
=cut |
|||
|
|||
sub search { |
|||
my ( $self, $where, $attr ) = @_; |
|||
|
|||
unless ( exists $attr->{order_by} ) { |
|||
$attr->{order_by} = |
|||
[ { '-asc' => 'register_id' }, { '-desc' => 'timestamp' } ]; |
|||
} |
|||
|
|||
my $rs = $self->SUPER::search({ code => 'CASHUP' }); |
|||
return $rs->SUPER::search( $where, $attr ); |
|||
} |
|||
|
|||
=head3 object_class |
|||
|
|||
=cut |
|||
|
|||
sub object_class { |
|||
return 'Koha::Cash::Register::Cashup'; |
|||
} |
|||
|
|||
1; |
@ -0,0 +1,99 @@ |
|||
package Koha::REST::V1::CashRegisters::Cashups; |
|||
|
|||
# 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 Mojo::Base 'Mojolicious::Controller'; |
|||
|
|||
use Scalar::Util qw(blessed); |
|||
use Try::Tiny; |
|||
|
|||
use Koha::Cash::Registers; |
|||
|
|||
=head1 NAME |
|||
|
|||
Koha::REST::V1::CashRegisters::Cashups |
|||
|
|||
=head1 API |
|||
|
|||
=head2 Methods |
|||
|
|||
=head3 list |
|||
|
|||
Controller function that handles retrieving a cash registers cashup actions |
|||
|
|||
=cut |
|||
|
|||
sub list { |
|||
my $c = shift->openapi->valid_input or return; |
|||
|
|||
my $register = Koha::Cash::Registers->find( |
|||
{ |
|||
id => $c->validation->param('cash_register_id') |
|||
} |
|||
); |
|||
|
|||
unless ($register) { |
|||
return $c->render( |
|||
status => 404, |
|||
openapi => { |
|||
error => "Register not found" |
|||
} |
|||
); |
|||
} |
|||
|
|||
return try { |
|||
my $cashups_rs = $register->cashups; |
|||
my $cashups = $c->objects->search($cashups_rs); |
|||
return $c->render( status => 200, openapi => $cashups ); |
|||
} |
|||
catch { |
|||
$c->unhandled_exception($_); |
|||
}; |
|||
} |
|||
|
|||
=head3 get |
|||
|
|||
Controller function that handles retrieving a cash register cashup |
|||
|
|||
=cut |
|||
|
|||
sub get { |
|||
my $c = shift->openapi->valid_input or return; |
|||
|
|||
return try { |
|||
my $cashup = Koha::Cash::Register::Cashups->find( |
|||
$c->validation->param('cashup_id') ); |
|||
unless ($cashup) { |
|||
return $c->render( |
|||
status => 404, |
|||
openapi => { error => "Cashup not found" } |
|||
); |
|||
} |
|||
|
|||
my $embed = $c->stash('koha.embed'); |
|||
return $c->render( |
|||
status => 200, |
|||
openapi => $cashup->to_api( { embed => $embed } ) |
|||
); |
|||
} |
|||
catch { |
|||
$c->unhandled_exception($_); |
|||
} |
|||
} |
|||
|
|||
1; |
@ -0,0 +1,30 @@ |
|||
{ |
|||
"type": "object", |
|||
"properties": { |
|||
"cashup_id": { |
|||
"type": "integer", |
|||
"description": "Internal cashup identifier" |
|||
}, |
|||
"cash_register_id": { |
|||
"type": "integer", |
|||
"description": "Internal identifier for the register the cashup belongs to" |
|||
}, |
|||
"manager_id": { |
|||
"type": "integer", |
|||
"description": "Internal identifier for the manager the cashup was performed by" |
|||
}, |
|||
"amount": { |
|||
"type": "number", |
|||
"description": "Account line amount" |
|||
}, |
|||
"timestamp": { |
|||
"type": "string", |
|||
"format": "date-time", |
|||
"description": "Timestamp for the latest line update" |
|||
}, |
|||
"summary": { |
|||
"type": "object", |
|||
"description": "A summary of the cashup action" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"cash_register_id_pp": { |
|||
"name": "cash_register_id", |
|||
"in": "path", |
|||
"description": "Cash register internal identifier", |
|||
"required": true, |
|||
"type": "integer" |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"cashup_id_pp": { |
|||
"name": "cashup_id", |
|||
"in": "path", |
|||
"description": "Cashup internal identifier", |
|||
"required": true, |
|||
"type": "integer" |
|||
} |
|||
} |
@ -0,0 +1,104 @@ |
|||
{ |
|||
"/cash_registers/{cash_register_id}/cashups": { |
|||
"get": { |
|||
"x-mojo-to": "CashRegisters::Cashups#list", |
|||
"operationId": "listCashups", |
|||
"tags": ["cash_registers", "cashups"], |
|||
"produces": ["application/json"], |
|||
"parameters": [{ |
|||
"$ref": "../parameters.json#/cash_register_id_pp" |
|||
}, |
|||
{ |
|||
"$ref": "../parameters.json#/match" |
|||
}, |
|||
{ |
|||
"$ref": "../parameters.json#/order_by" |
|||
}, |
|||
{ |
|||
"$ref": "../parameters.json#/page" |
|||
}, |
|||
{ |
|||
"$ref": "../parameters.json#/per_page" |
|||
}, |
|||
{ |
|||
"$ref": "../parameters.json#/q_param" |
|||
}, |
|||
{ |
|||
"$ref": "../parameters.json#/q_body" |
|||
}, |
|||
{ |
|||
"$ref": "../parameters.json#/q_header" |
|||
} |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "Cashups performed on this register", |
|||
"schema": { |
|||
"type": "array", |
|||
"items": { |
|||
"$ref": "../definitions.json#/cashup" |
|||
} |
|||
} |
|||
}, |
|||
"403": { |
|||
"description": "Access forbidden", |
|||
"schema": { |
|||
"$ref": "../definitions.json#/error" |
|||
} |
|||
}, |
|||
"404": { |
|||
"description": "Register not found", |
|||
"schema": { |
|||
"$ref": "../definitions.json#/error" |
|||
} |
|||
} |
|||
}, |
|||
"x-koha-authorization": { |
|||
"permissions": { |
|||
"cash_management": "cashup" |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
"/cashups/{cashup_id}": { |
|||
"get": { |
|||
"x-mojo-to": "CashRegisters::Cashups#get", |
|||
"operationId": "getCashup", |
|||
"tags": ["cash_registers", "cashups"], |
|||
"parameters": [{ |
|||
"$ref": "../parameters.json#/cashup_id_pp" |
|||
}], |
|||
"produces": [ |
|||
"application/json" |
|||
], |
|||
"responses": { |
|||
"200": { |
|||
"description": "A cashup", |
|||
"schema": { |
|||
"$ref": "../definitions.json#/cashup" |
|||
} |
|||
}, |
|||
"403": { |
|||
"description": "Access forbidden", |
|||
"schema": { |
|||
"$ref": "../definitions.json#/error" |
|||
} |
|||
}, |
|||
"404": { |
|||
"description": "Patron not found", |
|||
"schema": { |
|||
"$ref": "../definitions.json#/error" |
|||
} |
|||
} |
|||
}, |
|||
"x-koha-authorization": { |
|||
"permissions": { |
|||
"cash_management": "cashup" |
|||
} |
|||
}, |
|||
"x-koha-embed": [ |
|||
"summary" |
|||
] |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,352 @@ |
|||
#!/usr/bin/perl |
|||
|
|||
# Copyright 2020 Koha Development team |
|||
# |
|||
# 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 Test::More tests => 3; |
|||
|
|||
use Koha::Database; |
|||
|
|||
use t::lib::TestBuilder; |
|||
|
|||
my $builder = t::lib::TestBuilder->new; |
|||
my $schema = Koha::Database->new->schema; |
|||
|
|||
subtest 'manager' => sub { |
|||
plan tests => 2; |
|||
|
|||
$schema->storage->txn_begin; |
|||
|
|||
my $manager = $builder->build_object( { class => 'Koha::Patrons' } ); |
|||
my $cashup = $builder->build_object( |
|||
{ |
|||
class => 'Koha::Cash::Register::Cashups', |
|||
value => { manager_id => $manager->borrowernumber }, |
|||
} |
|||
); |
|||
|
|||
is( ref( $cashup->manager ), |
|||
'Koha::Patron', |
|||
'Koha::Cash::Register::Cashup->manager should return a Koha::Patron' ); |
|||
|
|||
is( $cashup->manager->id, $manager->id, |
|||
'Koha::Cash::Register::Cashup->manager returns the correct Koha::Patron' |
|||
); |
|||
|
|||
$schema->storage->txn_rollback; |
|||
|
|||
}; |
|||
|
|||
subtest 'register' => sub { |
|||
plan tests => 2; |
|||
|
|||
$schema->storage->txn_begin; |
|||
|
|||
my $register = |
|||
$builder->build_object( { class => 'Koha::Cash::Registers' } ); |
|||
my $cashup = $builder->build_object( |
|||
{ |
|||
class => 'Koha::Cash::Register::Cashups', |
|||
value => { register_id => $register->id }, |
|||
} |
|||
); |
|||
|
|||
is( |
|||
ref( $cashup->register ), |
|||
'Koha::Cash::Register', |
|||
'Koha::Cash::Register::Cashup->register should return a Koha::Cash::Register' |
|||
); |
|||
|
|||
is( $cashup->register->id, $register->id, |
|||
'Koha::Cash::Register::Cashup->register returns the correct Koha::Cash::Register' |
|||
); |
|||
|
|||
$schema->storage->txn_rollback; |
|||
|
|||
}; |
|||
|
|||
subtest 'summary' => sub { |
|||
plan tests => 29; |
|||
|
|||
$schema->storage->txn_begin; |
|||
|
|||
my $register = |
|||
$builder->build_object( { class => 'Koha::Cash::Registers' } ); |
|||
my $patron = $builder->build_object( { class => 'Koha::Patrons' } ); |
|||
my $manager = $builder->build_object( { class => 'Koha::Patrons' } ); |
|||
my $account = $patron->account; |
|||
my $expected_total = 0; |
|||
my $expected_income_total = 0; |
|||
my $expected_income_grouped = []; |
|||
my $expected_payout_total = 0; |
|||
my $expected_payout_grouped = []; |
|||
|
|||
# Transaction 1 (Fine (1.00) + Payment (-1.00)) |
|||
my $fine1 = $account->add_debit( |
|||
{ |
|||
amount => '1.00', |
|||
type => 'OVERDUE', |
|||
interface => 'cron' |
|||
} |
|||
); |
|||
$fine1->date( \'NOW() - INTERVAL 20 MINUTE' )->store; |
|||
|
|||
my $payment1 = $account->pay( |
|||
{ |
|||
cash_register => $register->id, |
|||
amount => '1.00', |
|||
credit_type => 'PAYMENT', |
|||
lines => [$fine1] |
|||
} |
|||
); |
|||
$payment1 = Koha::Account::Lines->find( $payment1->{payment_id} ); |
|||
$payment1->date( \'NOW() - INTERVAL 15 MINUTE' )->store; |
|||
$expected_income_total += '1.00'; |
|||
|
|||
# Overdue of 1.0 fully paid |
|||
unshift @{$expected_income_grouped}, |
|||
{ |
|||
debit_type_code => 'OVERDUE', |
|||
total => '1', |
|||
debit_type => { description => 'Overdue fine' } |
|||
}; |
|||
|
|||
# Transaction 2 (Account (1.00) + Lost (0.50) + Payment (-1.50)) |
|||
my $account1 = $account->add_debit( |
|||
{ |
|||
amount => '1.00', |
|||
type => 'ACCOUNT', |
|||
interface => 'cron' |
|||
} |
|||
); |
|||
$account1->date( \'NOW() - INTERVAL 13 MINUTE' )->store; |
|||
my $lost1 = $account->add_debit( |
|||
{ |
|||
amount => '0.50', |
|||
type => 'LOST', |
|||
interface => 'cron' |
|||
} |
|||
); |
|||
$lost1->date( \'NOW() - INTERVAL 13 MINUTE' )->store; |
|||
my $payment2 = $account->pay( |
|||
{ |
|||
cash_register => $register->id, |
|||
amount => '1.50', |
|||
credit_type => 'PAYMENT', |
|||
lines => [ $account1, $lost1 ] |
|||
} |
|||
); |
|||
$payment2 = Koha::Account::Lines->find( $payment2->{payment_id} ); |
|||
$payment2->date( \'NOW() - INTERVAL 13 MINUTE' )->store; |
|||
$expected_income_total += '1.5'; |
|||
|
|||
# Lost charge of 0.5 fully paid |
|||
unshift @{$expected_income_grouped}, |
|||
{ |
|||
debit_type_code => 'LOST', |
|||
total => '0.5', |
|||
debit_type => { description => 'Lost item' } |
|||
}; |
|||
|
|||
# Account fee of 1.0 fully paid |
|||
unshift @{$expected_income_grouped}, |
|||
{ |
|||
debit_type_code => 'ACCOUNT', |
|||
total => '1', |
|||
debit_type => { description => 'Account creation fee' } |
|||
}; |
|||
|
|||
# Transaction 3 (Refund (-0.50) + Payout (0.50)) |
|||
$lost1->discard_changes; |
|||
my $refund1 = $lost1->reduce( |
|||
{ |
|||
amount => '0.50', |
|||
reduction_type => 'REFUND', |
|||
interface => 'cron' |
|||
} |
|||
); |
|||
$refund1->date( \'NOW() - INTERVAL 13 MINUTE' )->store; |
|||
|
|||
my $payout1 = $refund1->payout( |
|||
{ |
|||
cash_register => $register->id, |
|||
amount => '0.50', |
|||
payout_type => 'CASH', |
|||
interface => 'intranet', |
|||
staff_id => $manager->borrowernumber, |
|||
branch => $manager->branchcode |
|||
} |
|||
); |
|||
$payout1->date( \'NOW() - INTERVAL 13 MINUTE' )->store; |
|||
$expected_payout_total += '0.5'; |
|||
|
|||
# Lost fee of 0.50 fully refunded |
|||
unshift @{$expected_payout_grouped}, |
|||
{ |
|||
'total' => '0.5', |
|||
'credit_type' => { |
|||
'description' => 'A refund applied to a patrons fine' |
|||
}, |
|||
'credit_type_code' => 'REFUND' |
|||
}; |
|||
|
|||
$expected_total += $expected_income_total; |
|||
$expected_total -= $expected_payout_total; |
|||
|
|||
diag("Cashup 1"); |
|||
my $cashup1 = |
|||
$register->add_cashup( { manager_id => $manager->id, amount => '2.00' } ); |
|||
|
|||
my $summary = $cashup1->summary; |
|||
|
|||
is( $summary->{from_date}, undef, "from_date is undefined if there is only one recorded" ); |
|||
is( $summary->{to_date}, $cashup1->timestamp, "to_date equals cashup timestamp" ); |
|||
is( ref( $summary->{income_grouped} ), 'ARRAY', "income_grouped contains an arrayref" ); |
|||
is( scalar @{ $summary->{income_grouped} }, 3, "income_grouped contains 3 transactions" ); |
|||
is_deeply( $summary->{income_grouped}, $expected_income_grouped, "income_grouped arrayref is correct" ); |
|||
is( $summary->{income_total}, $expected_income_total, "income_total is correct" ); |
|||
|
|||
is( ref( $summary->{payout_grouped} ), 'ARRAY', "payout_grouped contains an arrayref" ); |
|||
is( scalar @{ $summary->{payout_grouped} }, 1, "payout_grouped contains 1 transaction" ); |
|||
is_deeply( $summary->{payout_grouped}, $expected_payout_grouped, "payout_grouped arrayref is correct" ); |
|||
is( $summary->{payout_total}, $expected_payout_total, "payout_total is correct" ); |
|||
is( $summary->{total}, $expected_total,"total equals expected_total" ); |
|||
|
|||
# Backdate cashup1 so we can add a new cashup to check 'previous' |
|||
$cashup1->timestamp( \'NOW() - INTERVAL 12 MINUTE' )->store(); |
|||
$cashup1->discard_changes; |
|||
$expected_total = 0; |
|||
$expected_income_total = 0; |
|||
$expected_income_grouped = []; |
|||
$expected_payout_total = 0; |
|||
$expected_payout_grouped = []; |
|||
|
|||
# Transaction 4 ( Fine (2.75) + Partial payment (-2.00) ) |
|||
my $fine2 = $account->add_debit( |
|||
{ |
|||
amount => '2.75', |
|||
type => 'OVERDUE', |
|||
interface => 'cron' |
|||
} |
|||
); |
|||
$fine2->date( \'NOW() - INTERVAL 10 MINUTE' )->store; |
|||
|
|||
my $payment3 = $account->pay( |
|||
{ |
|||
cash_register => $register->id, |
|||
amount => '2.00', |
|||
credit_type => 'PAYMENT', |
|||
lines => [$fine2] |
|||
} |
|||
); |
|||
$payment3 = Koha::Account::Lines->find( $payment3->{payment_id} ); |
|||
$payment3->date( \'NOW() - INTERVAL 10 MINUTE' )->store; |
|||
$expected_income_total += '2.00'; |
|||
|
|||
unshift @{$expected_income_grouped}, |
|||
{ |
|||
debit_type_code => 'OVERDUE', |
|||
total => '-2.000000' * -1, |
|||
debit_type => { 'description' => 'Overdue fine' } |
|||
}; |
|||
|
|||
$expected_total += $expected_income_total; |
|||
$expected_total -= $expected_payout_total; |
|||
|
|||
diag("Cashup 2"); |
|||
my $cashup2 = |
|||
$register->add_cashup( { manager_id => $manager->id, amount => '2.00' } ); |
|||
|
|||
$summary = $cashup2->summary; |
|||
|
|||
is( $summary->{from_date}, $cashup1->timestamp, "from_date returns the timestamp of the previous cashup cashup" ); |
|||
is( $summary->{to_date}, $cashup2->timestamp, "to_date equals cashup timestamp" ); |
|||
is( ref( $summary->{income_grouped} ), 'ARRAY', "income_grouped contains Koha::Account::Lines" ); |
|||
is( scalar @{ $summary->{income_grouped} }, 1, "income_grouped contains 1 transaction" ); |
|||
is_deeply( $summary->{income_grouped}, $expected_income_grouped, "income_grouped arrayref is correct for partial payment" ); |
|||
is( ref( $summary->{payout_grouped} ), 'ARRAY', "payout_grouped contains Koha::Account::Lines" ); |
|||
is( scalar @{ $summary->{payout_grouped} }, 0, "payout_grouped contains 0 transactions" ); |
|||
is_deeply( $summary->{payout_grouped}, $expected_payout_grouped, "payout_grouped arrayref is correct" ); |
|||
is( $summary->{total}, $expected_total, "total equals expected_total" ); |
|||
|
|||
# Backdate cashup2 so we can add a new cashup to check |
|||
$cashup2->timestamp( \'NOW() - INTERVAL 6 MINUTE' )->store(); |
|||
$cashup2->discard_changes; |
|||
$expected_total = 0; |
|||
$expected_income_total = 0; |
|||
$expected_income_grouped = []; |
|||
$expected_payout_total = 0; |
|||
$expected_payout_grouped = []; |
|||
|
|||
# Transaction 5 (Refund (-1) + Payout (1)) |
|||
$account1->discard_changes; |
|||
my $refund2 = $account1->reduce( |
|||
{ |
|||
amount => '1.00', |
|||
reduction_type => 'REFUND', |
|||
interface => 'cron' |
|||
} |
|||
); |
|||
$refund2->date( \'NOW() - INTERVAL 3 MINUTE' )->store; |
|||
|
|||
my $payout2 = $refund2->payout( |
|||
{ |
|||
cash_register => $register->id, |
|||
amount => '1.00', |
|||
payout_type => 'CASH', |
|||
interface => 'intranet', |
|||
staff_id => $manager->borrowernumber, |
|||
branch => $manager->branchcode |
|||
} |
|||
); |
|||
$payout2->date( \'NOW() - INTERVAL 3 MINUTE' )->store; |
|||
$expected_payout_total += '1.00'; |
|||
|
|||
# Account fee of 1.00 fully refunded (Accross cashup boundary) |
|||
unshift @{$expected_payout_grouped}, |
|||
{ |
|||
'total' => '1', |
|||
'credit_type' => { |
|||
'description' => 'A refund applied to a patrons fine' |
|||
}, |
|||
'credit_type_code' => 'REFUND' |
|||
}; |
|||
|
|||
$expected_total += $expected_income_total; |
|||
$expected_total -= $expected_payout_total; |
|||
|
|||
diag("Cashup 3"); |
|||
my $cashup3 = |
|||
$register->add_cashup( { manager_id => $manager->id, amount => '2.00' } ); |
|||
|
|||
$summary = $cashup3->summary; |
|||
|
|||
is( $summary->{from_date}, $cashup2->timestamp, "from_date returns the timestamp of the previous cashup cashup" ); |
|||
is( $summary->{to_date}, $cashup3->timestamp, "to_date equals cashup timestamp" ); |
|||
is( ref( $summary->{income_grouped} ), 'ARRAY', "income_grouped contains Koha::Account::Lines" ); |
|||
is( scalar @{ $summary->{income_grouped} }, 0, "income_grouped contains 1 transaction" ); |
|||
is_deeply( $summary->{income_grouped}, $expected_income_grouped, "income_grouped arrayref is correct for partial payment" ); |
|||
is( ref( $summary->{payout_grouped} ), 'ARRAY', "payout_grouped contains Koha::Account::Lines" ); |
|||
is( scalar @{ $summary->{payout_grouped} }, 1, "payout_grouped contains 0 transactions" ); |
|||
is_deeply( $summary->{payout_grouped}, $expected_payout_grouped, "payout_grouped arrayref is correct" ); |
|||
is( $summary->{total}, $expected_total, "total equals expected_total" ); |
|||
|
|||
$schema->storage->txn_rollback; |
|||
}; |
|||
|
|||
1; |
@ -0,0 +1,76 @@ |
|||
#!/usr/bin/perl |
|||
|
|||
# Copyright 2020 Koha Development team |
|||
# |
|||
# 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 Test::More tests => 1; |
|||
|
|||
use Koha::Database; |
|||
|
|||
use t::lib::TestBuilder; |
|||
|
|||
my $builder = t::lib::TestBuilder->new; |
|||
my $schema = Koha::Database->new->schema; |
|||
|
|||
subtest 'search' => sub { |
|||
plan tests => 3; |
|||
|
|||
$schema->storage->txn_begin; |
|||
|
|||
my $manager = $builder->build_object( { class => 'Koha::Patrons' } ); |
|||
my $register = |
|||
$builder->build_object( { class => 'Koha::Cash::Registers' } ); |
|||
my $cashup1 = $builder->build_object( |
|||
{ |
|||
class => 'Koha::Cash::Register::Actions', |
|||
value => { |
|||
manager_id => $manager->borrowernumber, |
|||
register_id => $register->id, |
|||
code => 'CASHOUT' |
|||
}, |
|||
} |
|||
); |
|||
my $cashup2 = $builder->build_object( |
|||
{ |
|||
class => 'Koha::Cash::Register::Actions', |
|||
value => { |
|||
manager_id => $manager->borrowernumber, |
|||
register_id => $register->id, |
|||
code => 'CASHUP' |
|||
}, |
|||
} |
|||
); |
|||
|
|||
my $cashups = Koha::Cash::Register::Cashups->search(); |
|||
|
|||
is( |
|||
ref($cashups), |
|||
'Koha::Cash::Register::Cashups', |
|||
'Returns a Koha::Cash::Register::Cashups resultset' |
|||
); |
|||
is( $cashups->count, 1, 'Returns only CASHUP actions' ); |
|||
is( |
|||
ref( $cashups->next ), |
|||
'Koha::Cash::Register::Cashup', |
|||
'Result is a Koha::Cash::Register::Cashup object' |
|||
); |
|||
|
|||
$schema->storage->txn_rollback; |
|||
}; |
|||
|
|||
1; |
@ -0,0 +1,177 @@ |
|||
#!/usr/bin/env perl |
|||
|
|||
# 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 Test::More tests => 2; |
|||
use Test::Mojo; |
|||
|
|||
use t::lib::TestBuilder; |
|||
use t::lib::Mocks; |
|||
|
|||
use Koha::Cash::Register::Cashups; |
|||
use Koha::Cash::Register::Cashups; |
|||
use Koha::Database; |
|||
|
|||
my $schema = Koha::Database->new->schema; |
|||
my $builder = t::lib::TestBuilder->new; |
|||
|
|||
my $t = Test::Mojo->new('Koha::REST::V1'); |
|||
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 ); |
|||
|
|||
subtest 'list() tests' => sub { |
|||
|
|||
plan tests => 17; |
|||
|
|||
$schema |