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

(-)a/Koha/Cash/Register.pm (-4 / +5 lines)
Lines 22-27 use Carp; Link Here
22
use Koha::Account::Lines;
22
use Koha::Account::Lines;
23
use Koha::Account::Offsets;
23
use Koha::Account::Offsets;
24
use Koha::Cash::Register::Actions;
24
use Koha::Cash::Register::Actions;
25
use Koha::Cash::Register::Cashups;
25
use Koha::Database;
26
use Koha::Database;
26
27
27
use base qw(Koha::Object);
28
use base qw(Koha::Object);
Lines 66-72 sub cashups { Link Here
66
      $self->_result->search_related( 'cash_register_actions',
67
      $self->_result->search_related( 'cash_register_actions',
67
        $merged_conditions, $attrs );
68
        $merged_conditions, $attrs );
68
69
69
    return Koha::Cash::Register::Actions->_new_from_dbic($rs);
70
    return Koha::Cash::Register::Cashups->_new_from_dbic($rs);
70
}
71
}
71
72
72
=head3 last_cashup
73
=head3 last_cashup
Lines 85-91 sub last_cashup { Link Here
85
    )->single;
86
    )->single;
86
87
87
    return unless $rs;
88
    return unless $rs;
88
    return Koha::Cash::Register::Action->_new_from_dbic($rs);
89
    return Koha::Cash::Register::Cashup->_new_from_dbic($rs);
89
}
90
}
90
91
91
=head3 accountlines
92
=head3 accountlines
Lines 208-214 sub drop_default { Link Here
208
209
209
=head3 add_cashup
210
=head3 add_cashup
210
211
211
    my $action = $cash_register->add_cashup(
212
    my $cashup = $cash_register->add_cashup(
212
        {
213
        {
213
            manager_id => $logged_in_user->id,
214
            manager_id => $logged_in_user->id,
214
            amount     => $cash_register->outstanding_accountlines->total
215
            amount     => $cash_register->outstanding_accountlines->total
Lines 230-236 sub add_cashup { Link Here
230
        }
231
        }
231
    )->discard_changes;
232
    )->discard_changes;
232
233
233
    return Koha::Cash::Register::Action->_new_from_dbic($rs);
234
    return Koha::Cash::Register::Cashup->_new_from_dbic($rs);
234
}
235
}
235
236
236
=head2 Internal methods
237
=head2 Internal methods
(-)a/Koha/Cash/Register/Action.pm (-97 lines)
Lines 60-162 sub register { Link Here
60
    return Koha::Cash::Register->_new_from_dbic($rs);
60
    return Koha::Cash::Register->_new_from_dbic($rs);
61
}
61
}
62
62
63
=head3 cashup_summary
64
65
  my $cashup_summary = $action->cashup_summary;
66
67
Return a hashref containing a summary of transactions that make up this cashup action.
68
69
=cut
70
71
sub cashup_summary {
72
    my ($self) = @_;
73
    my $summary;
74
    my $prior_cashup = Koha::Cash::Register::Actions->search(
75
        {
76
            'code'      => 'CASHUP',
77
            'timestamp' => { '<' => $self->timestamp },
78
            register_id => $self->register_id
79
        },
80
        {
81
            order_by => { '-desc' => [ 'timestamp', 'id' ] },
82
            rows     => 1
83
        }
84
    )->single;
85
86
    my $conditions =
87
      $prior_cashup
88
      ? {
89
        'date' => {
90
            '-between' =>
91
              [ $prior_cashup->timestamp, $self->timestamp ]
92
        }
93
      }
94
      : { 'date' => { '<' => $self->timestamp } };
95
96
    my $outgoing_transactions = $self->register->accountlines->search(
97
        { %{$conditions}, credit_type_code => undef },
98
    );
99
    my $income_transactions = $self->register->accountlines->search(
100
        { %{$conditions}, debit_type_code => undef },
101
    );
102
103
    my $income_summary = Koha::Account::Offsets->search(
104
        {
105
            'me.credit_id' =>
106
              { '-in' => [ $income_transactions->get_column('accountlines_id') ] },
107
            'me.debit_id' => { '!=' => undef }
108
        },
109
        {
110
            join     => { 'debit' => 'debit_type_code' },
111
            group_by => [ 'debit.debit_type_code', 'debit_type_code.description' ],
112
            order_by => { '-asc' => 'debit.debit_type_code' },
113
            'select' => [ { sum => 'me.amount' }, 'debit.debit_type_code', 'debit_type_code.description' ],
114
            'as'     => [ 'total', 'debit_type_code', 'debit_description' ],
115
        }
116
    );
117
118
    my $outgoing_summary = Koha::Account::Offsets->search(
119
        {
120
            'me.debit_id' =>
121
              { '-in' => [ $outgoing_transactions->get_column('accountlines_id') ] },
122
            'me.credit_id' => { '!=' => undef }
123
        },
124
        {
125
            join     => { 'credit' => 'credit_type_code' },
126
            group_by => [ 'credit.credit_type_code', 'credit_type_code.description' ],
127
            order_by => { '-asc' => 'credit.credit_type_code' },
128
            'select' => [ { sum => 'me.amount' }, 'credit.credit_type_code', 'credit_type_code.description' ],
129
            'as'     => [ 'total', 'credit_type_code', 'credit_description' ],
130
        }
131
    );
132
133
    my @income = map {
134
        {
135
            total           => $_->get_column('total'),
136
            debit_type_code => $_->get_column('debit_type_code'),
137
            debit_type      => { description => $_->get_column('debit_description') }
138
        }
139
    } $income_summary->as_list;
140
    my @outgoing = map {
141
        {
142
            total            => $_->get_column('total'),
143
            credit_type_code => $_->get_column('credit_type_code'),
144
            credit_type      => { description => $_->get_column('credit_description') }
145
        }
146
    } $outgoing_summary->as_list;
147
148
    $summary = {
149
        from_date             => $prior_cashup? $prior_cashup->timestamp : undef,
150
        to_date               => $self->timestamp,
151
        income                => \@income,
152
        outgoing              => \@outgoing,
153
        income_transactions   => $income_transactions,
154
        outgoing_transactions => $outgoing_transactions,
155
    };
156
157
    return $summary;
158
}
159
160
=head2 Internal methods
63
=head2 Internal methods
161
64
162
=cut
65
=cut
(-)a/Koha/Cash/Register/Cashup.pm (+208 lines)
Line 0 Link Here
1
package Koha::Cash::Register::Cashup;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Carp;
21
22
use Koha::Database;
23
24
use base qw(Koha::Cash::Register::Action);
25
26
=head1 NAME
27
28
Koha::Cash::Register::Actions - Koha Cash Register Action Object set class
29
30
=head1 API
31
32
=head2 Class methods
33
34
=head3 search
35
36
    my $cashup = Koha::Cash::Register::Actions::Cashup->search( $where, $attr );
37
38
Returns a list of cash register cashup.
39
40
=cut
41
42
sub search {
43
    my ( $self, $where, $attr ) = @_;
44
45
    $where->{code} = 'CASHUP';
46
47
    unless ( exists $attr->{order_by} ) {
48
        $attr->{order_by} =
49
          [ { '-asc' => 'register_id' }, { '-desc' => 'timestamp' } ];
50
    }
51
52
    return $self->SUPER::search( $where, $attr );
53
}
54
55
=head3 summary
56
57
  my $summary = $cashup->summary;
58
59
Return a hashref containing a summary of transactions that make up this cashup.
60
61
=cut
62
63
sub summary {
64
    my ($self) = @_;
65
    my $summary;
66
    my $prior_cashup = Koha::Cash::Register::Cashups->search(
67
        {
68
            'timestamp' => { '<' => $self->timestamp },
69
            register_id => $self->register_id
70
        },
71
        {
72
            order_by => { '-desc' => [ 'timestamp', 'id' ] },
73
            rows     => 1
74
        }
75
    );
76
77
    my $previous = $prior_cashup->single;
78
79
    my $conditions =
80
      $previous
81
      ? {
82
        'date' => {
83
            '-between' =>
84
              [ $previous->_result->get_column('timestamp'), $self->timestamp ]
85
        }
86
      }
87
      : { 'date' => { '<' => $self->timestamp } };
88
89
    my $payout_transactions = $self->register->accountlines->search(
90
        { %{$conditions}, credit_type_code => undef },
91
    );
92
    my $income_transactions = $self->register->accountlines->search(
93
        { %{$conditions}, debit_type_code => undef },
94
    );
95
96
    my $income_summary = Koha::Account::Offsets->search(
97
        {
98
            'me.credit_id' => {
99
                '-in' => $income_transactions->_resultset->get_column(
100
                    'accountlines_id')->as_query
101
            },
102
            'me.debit_id' => { '!=' => undef }
103
        },
104
        {
105
            join => { 'debit' => 'debit_type_code' },
106
            group_by =>
107
              [ 'debit.debit_type_code', 'debit_type_code.description' ],
108
            'select' => [
109
                { sum => 'me.amount' }, 'debit.debit_type_code',
110
                'debit_type_code.description'
111
            ],
112
            'as' => [ 'total', 'debit_type_code', 'debit_description' ],
113
        }
114
    );
115
116
    my $payout_summary = Koha::Account::Offsets->search(
117
        {
118
            'me.debit_id' => {
119
                '-in' => $payout_transactions->_resultset->get_column(
120
                    'accountlines_id')->as_query
121
            },
122
            'me.credit_id' => { '!=' => undef }
123
        },
124
        {
125
            join => { 'credit' => 'credit_type_code' },
126
            group_by =>
127
              [ 'credit.credit_type_code', 'credit_type_code.description' ],
128
            'select' => [
129
                { sum => 'me.amount' }, 'credit.credit_type_code',
130
                'credit_type_code.description'
131
            ],
132
            'as' => [ 'total', 'credit_type_code', 'credit_description' ],
133
        }
134
    );
135
136
    my @income = map {
137
        {
138
            total           => $_->get_column('total') * -1,
139
            debit_type_code => $_->get_column('debit_type_code'),
140
            debit_type => { description => $_->get_column('debit_description') }
141
        }
142
    } $income_summary->as_list;
143
    my @payout = map {
144
        {
145
            total            => $_->get_column('total') * -1,
146
            credit_type_code => $_->get_column('credit_type_code'),
147
            credit_type =>
148
              { description => $_->get_column('credit_description') }
149
        }
150
    } $payout_summary->as_list;
151
152
    my $income_total = $income_transactions->total;
153
    my $payout_total = $payout_transactions->total;
154
    my $total        = ( $income_total + $payout_total );
155
156
    my $payment_types = Koha::AuthorisedValues->search(
157
        {
158
            category => 'PAYMENT_TYPE'
159
        },
160
        {
161
            order_by => ['lib'],
162
        }
163
    );
164
165
    my @total_grouped;
166
    for my $type ( $payment_types->as_list ) {
167
        my $typed_income = $income_transactions->total( { payment_type => $type->authorised_value } );
168
        my $typed_payout = $payout_transactions->total( { payment_type => $type->authorised_value } );
169
        my $typed_total = ( $typed_income + $typed_payout );
170
        push @total_grouped, { payment_type => $type->lib, total => $typed_total };
171
    }
172
173
    $summary = {
174
        from_date      => $previous ? $previous->timestamp : undef,
175
        to_date        => $self->timestamp,
176
        income_grouped => \@income,
177
        income_total   => abs($income_total),
178
        payout_grouped => \@payout,
179
        payout_total   => abs($payout_total),
180
        total          => $total * -1,
181
        total_grouped  => \@total_grouped
182
    };
183
184
    return $summary;
185
}
186
187
=head3 to_api_mapping
188
189
This method returns the mapping for representing a Koha::Cash::Regiser::Cashup object
190
on the API.
191
192
=cut
193
194
sub to_api_mapping {
195
    return {
196
        id          => 'cashup_id',
197
        register_id => 'cash_register_id',
198
        code        => undef
199
    };
200
}
201
202
1;
203
204
=head1 AUTHORS
205
206
Martin Renvoize <martin.renvoize@ptfs-europe.com>
207
208
=cut
(-)a/Koha/Cash/Register/Cashups.pm (+64 lines)
Line 0 Link Here
1
package Koha::Cash::Register::Cashups;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Carp;
21
22
use Koha::Database;
23
use Koha::Cash::Register::Cashup;
24
25
use base qw(Koha::Cash::Register::Actions);
26
27
=head1 NAME
28
29
Koha::Cash::Register::Actions - Koha Cash Register Action Object set class
30
31
=head1 API
32
33
=head2 Class methods
34
35
=head3 search
36
37
    my $cashups = Koha::Cash::Register::Cashups->search( $where, $attr );
38
39
Returns a list of cash register cashups.
40
41
=cut
42
43
sub search {
44
    my ( $self, $where, $attr ) = @_;
45
46
    $where->{code} = 'CASHUP';
47
48
    unless ( exists $attr->{order_by} ) {
49
        $attr->{order_by} =
50
          [ { '-asc' => 'register_id' }, { '-desc' => 'timestamp' } ];
51
    }
52
53
    return $self->SUPER::search( $where, $attr );
54
}
55
56
=head3 object_class
57
58
=cut
59
60
sub object_class {
61
    return 'Koha::Cash::Register::Cashup';
62
}
63
64
1;
(-)a/Koha/REST/V1/CashRegisters/Cashups.pm (+79 lines)
Line 0 Link Here
1
package Koha::REST::V1::CashRegisters::Cashups;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Mojo::Base 'Mojolicious::Controller';
21
22
use Scalar::Util qw(blessed);
23
use Try::Tiny;
24
25
use Koha::Cash::Register::Cashups;
26
27
=head1 NAME
28
29
Koha::REST::V1::CashRegisters::Cashups
30
31
=head1 API
32
33
=head2 Methods
34
35
=head3 list
36
37
=cut
38
39
sub list {
40
    my $c = shift->openapi->valid_input or return;
41
42
    return try {
43
        my $cashups_set = Koha::Cash::Register::Cashups->new;
44
        my $cashups = $c->objects->search( $cashups_set );
45
        return $c->render( status => 200, openapi => $cashups );
46
    }
47
    catch {
48
        $c->unhandled_exception($_);
49
    };
50
}
51
52
=head3 get
53
54
Controller function that handles retrieving a cash register cashup
55
56
=cut
57
58
sub get {
59
    my $c = shift->openapi->valid_input or return;
60
61
    return try {
62
        my $cashup = Koha::Cash::Register::Cashups->find( $c->validation->param('cashup_id') );
63
        unless ($cashup) {
64
            return $c->render( status  => 404,
65
                            openapi => { error => "Cashup not found" } );
66
        }
67
68
        my $embed = $c->stash('koha.embed');
69
        return $c->render(
70
            status  => 200,
71
            openapi => $cashup->to_api( { embed => $embed } )
72
        );
73
    }
74
    catch {
75
        $c->unhandled_exception($_);
76
    }
77
}
78
79
1;
(-)a/api/v1/swagger/definitions.json (+3 lines)
Lines 5-10 Link Here
5
  "basket": {
5
  "basket": {
6
    "$ref": "definitions/basket.json"
6
    "$ref": "definitions/basket.json"
7
  },
7
  },
8
  "cashup": {
9
    "$ref": "definitions/cashup.json"
10
  },
8
  "circ-rule-kind": {
11
  "circ-rule-kind": {
9
    "$ref": "definitions/circ-rule-kind.json"
12
    "$ref": "definitions/circ-rule-kind.json"
10
  },
13
  },
(-)a/api/v1/swagger/definitions/cashup.json (+30 lines)
Line 0 Link Here
1
{
2
    "type": "object",
3
    "properties": {
4
        "cashup_id": {
5
            "type": "integer",
6
            "description": "Internal cashup identifier"
7
        },
8
        "cash_register_id": {
9
            "type": "integer",
10
            "description": "Internal identifier for the register the cashup belongs to"
11
        },
12
        "manager_id": {
13
            "type": "integer",
14
            "description": "Internal identifier for the manager the cashup was performed by"
15
        },
16
        "amount": {
17
            "type": "number",
18
            "description": "Account line amount"
19
        },
20
        "timestamp": {
21
            "type": "string",
22
            "format": "date-time",
23
            "description": "Timestamp for the latest line update"
24
        },
25
        "summary": {
26
            "type": "object",
27
            "description": "A summary of the cashup action"
28
        }
29
    }
30
}
(-)a/api/v1/swagger/parameters.json (+6 lines)
Lines 44-49 Link Here
44
  "seen_pp": {
44
  "seen_pp": {
45
    "$ref": "parameters/checkout.json#/seen_pp"
45
    "$ref": "parameters/checkout.json#/seen_pp"
46
  },
46
  },
47
  "cash_register_id_pp": {
48
    "$ref": "parameters/cash_register.json#/cash_register_id_pp"
49
  },
50
  "cashup_id_pp": {
51
    "$ref": "parameters/cashup.json#/cashup_id_pp"
52
  },
47
  "match": {
53
  "match": {
48
    "name": "_match",
54
    "name": "_match",
49
    "in": "query",
55
    "in": "query",
(-)a/api/v1/swagger/parameters/cash_register.json (+9 lines)
Line 0 Link Here
1
{
2
    "cash_register_id_pp": {
3
      "name": "cash_register_id",
4
      "in": "path",
5
      "description": "Cash register internal identifier",
6
      "required": true,
7
      "type": "integer"
8
    }
9
}
(-)a/api/v1/swagger/parameters/cashup.json (+9 lines)
Line 0 Link Here
1
{
2
    "cashup_id_pp": {
3
      "name": "cashup_id",
4
      "in": "path",
5
      "description": "Cashup internal identifier",
6
      "required": true,
7
      "type": "integer"
8
    }
9
}
(-)a/api/v1/swagger/paths.json (+6 lines)
Lines 17-22 Link Here
17
  "/acquisitions/funds": {
17
  "/acquisitions/funds": {
18
    "$ref": "paths/acquisitions_funds.json#/~1acquisitions~1funds"
18
    "$ref": "paths/acquisitions_funds.json#/~1acquisitions~1funds"
19
  },
19
  },
20
  "/cash_registers/{cash_register_id}/cashups": {
21
    "$ref": "paths/cash_registers.json#/~1cash_registers~1{cash_register_id}~1cashups"
22
  },
23
  "/cashups/{cashup_id}": {
24
    "$ref": "paths/cash_registers.json#/~1cashups~1{cashup_id}"
25
  },
20
  "/checkouts": {
26
  "/checkouts": {
21
    "$ref": "paths/checkouts.json#/~1checkouts"
27
    "$ref": "paths/checkouts.json#/~1checkouts"
22
  },
28
  },
(-)a/api/v1/swagger/paths/cash_registers.json (+104 lines)
Line 0 Link Here
1
{
2
    "/cash_registers/{cash_register_id}/cashups": {
3
        "get": {
4
            "x-mojo-to": "CashRegisters::Cashups#list",
5
            "operationId": "listCashups",
6
            "tags": ["cash_registers", "cashups"],
7
            "produces": ["application/json"],
8
            "parameters": [{
9
                    "$ref": "../parameters.json#/cash_register_id_pp"
10
                },
11
                {
12
                    "$ref": "../parameters.json#/match"
13
                },
14
                {
15
                    "$ref": "../parameters.json#/order_by"
16
                },
17
                {
18
                    "$ref": "../parameters.json#/page"
19
                },
20
                {
21
                    "$ref": "../parameters.json#/per_page"
22
                },
23
                {
24
                    "$ref": "../parameters.json#/q_param"
25
                },
26
                {
27
                    "$ref": "../parameters.json#/q_body"
28
                },
29
                {
30
                    "$ref": "../parameters.json#/q_header"
31
                }
32
            ],
33
            "responses": {
34
                "200": {
35
                    "description": "Cashups performed on this register",
36
                    "schema": {
37
                        "type": "array",
38
                        "items": {
39
                            "$ref": "../definitions.json#/cashup"
40
                        }
41
                    }
42
                },
43
                "403": {
44
                    "description": "Access forbidden",
45
                    "schema": {
46
                        "$ref": "../definitions.json#/error"
47
                    }
48
                },
49
                "404": {
50
                    "description": "Register not found",
51
                    "schema": {
52
                        "$ref": "../definitions.json#/error"
53
                    }
54
                }
55
            },
56
            "x-koha-authorization": {
57
                "permissions": {
58
                    "cash_management": "cashup"
59
                }
60
            }
61
        }
62
    },
63
    "/cashups/{cashup_id}": {
64
        "get": {
65
            "x-mojo-to": "CashRegisters::Cashups#get",
66
            "operationId": "getCashup",
67
            "tags": ["cash_registers", "cashups"],
68
            "parameters": [{
69
                "$ref": "../parameters.json#/cashup_id_pp"
70
            }],
71
            "produces": [
72
                "application/json"
73
            ],
74
            "responses": {
75
                "200": {
76
                    "description": "A cashup",
77
                    "schema": {
78
                        "$ref": "../definitions.json#/cashup"
79
                    }
80
                },
81
                "403": {
82
                    "description": "Access forbidden",
83
                    "schema": {
84
                        "$ref": "../definitions.json#/error"
85
                    }
86
                },
87
                "404": {
88
                    "description": "Patron not found",
89
                    "schema": {
90
                        "$ref": "../definitions.json#/error"
91
                    }
92
                }
93
            },
94
            "x-koha-authorization": {
95
                "permissions": {
96
                    "cash_management": "cashup"
97
                }
98
            },
99
            "x-koha-embed": [
100
                "summary"
101
            ]
102
        }
103
    }
104
}
(-)a/t/db_dependent/Koha/Cash/Register/Action.t (-268 / +1 lines)
Lines 18-24 Link Here
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
19
20
use Modern::Perl;
20
use Modern::Perl;
21
use Test::More tests => 3;
21
use Test::More tests => 2;
22
22
23
use Koha::Database;
23
use Koha::Database;
24
24
Lines 80-350 subtest 'register' => sub { Link Here
80
80
81
};
81
};
82
82
83
subtest 'cashup_summary' => sub {
84
    plan tests => 14;
85
86
    $schema->storage->txn_begin;
87
88
    my $register =
89
      $builder->build_object( { class => 'Koha::Cash::Registers' } );
90
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
91
    my $manager = $builder->build_object( { class => 'Koha::Patrons' } );
92
93
    # Transaction 1
94
    my $debt1 = $builder->build_object(
95
        {
96
            class => 'Koha::Account::Lines',
97
            value => {
98
                register_id       => undef,
99
                amount            => '1.00',
100
                amountoutstanding => '0.00',
101
                credit_type_code  => undef,
102
                debit_type_code   => 'OVERDUE',
103
                date              => \'NOW() - INTERVAL 10 MINUTE'
104
            },
105
        }
106
    );
107
    my $income1 = $builder->build_object(
108
        {
109
            class => 'Koha::Account::Lines',
110
            value => {
111
                register_id       => $register->id,
112
                amount            => '-1.00',
113
                amountoutstanding => '0.00',
114
                credit_type_code  => 'PAYMENT',
115
                debit_type_code   => undef,
116
                date              => \'NOW() - INTERVAL 5 MINUTE'
117
            },
118
        }
119
    );
120
    $builder->build_object(
121
        {
122
            class => 'Koha::Account::Offsets',
123
            value => {
124
                credit_id => $income1->accountlines_id,
125
                debit_id  => $debt1->accountlines_id,
126
                amount    => '1.00',
127
                type      => 'Payment'
128
            },
129
        }
130
    );
131
132
    # Transaction 2
133
    my $debt2 = $builder->build_object(
134
        {
135
            class => 'Koha::Account::Lines',
136
            value => {
137
                register_id       => undef,
138
                amount            => '1.00',
139
                amountoutstanding => '0.00',
140
                credit_type_code  => undef,
141
                debit_type_code   => 'ACCOUNT',
142
                date              => \'NOW() - INTERVAL 3 MINUTE'
143
            },
144
        }
145
    );
146
    my $debt3 = $builder->build_object(
147
        {
148
            class => 'Koha::Account::Lines',
149
            value => {
150
                register_id       => undef,
151
                amount            => '0.50',
152
                amountoutstanding => '0.00',
153
                credit_type_code  => undef,
154
                debit_type_code   => 'LOST',
155
                date              => \'NOW() - INTERVAL 3 MINUTE'
156
            },
157
        }
158
    );
159
    my $income2 = $builder->build_object(
160
        {
161
            class => 'Koha::Account::Lines',
162
            value => {
163
                register_id       => $register->id,
164
                amount            => '-1.50',
165
                amountoutstanding => '0.00',
166
                credit_type_code  => 'PAYMENT',
167
                debit_type_code   => undef,
168
                date              => \'NOW() - INTERVAL 3 MINUTE'
169
            },
170
        }
171
    );
172
    $builder->build_object(
173
        {
174
            class => 'Koha::Account::Offsets',
175
            value => {
176
                credit_id => $income2->accountlines_id,
177
                debit_id  => $debt2->accountlines_id,
178
                amount    => '1.00',
179
                type      => 'Payment'
180
            },
181
        }
182
    );
183
    $builder->build_object(
184
        {
185
            class => 'Koha::Account::Offsets',
186
            value => {
187
                credit_id => $income2->accountlines_id,
188
                debit_id  => $debt3->accountlines_id,
189
                amount    => '0.50',
190
                type      => 'Payment'
191
            },
192
        }
193
    );
194
    my $expected_income = [
195
        {
196
            debit_type_code => 'ACCOUNT',
197
            total           => '1.000000',
198
            debit_type      => { 'description' => 'Account creation fee' }
199
        },
200
        {
201
            debit_type_code => 'LOST',
202
            total           => '0.500000',
203
            debit_type      => { description => 'Lost item' }
204
        },
205
        {
206
            debit_type_code => 'OVERDUE',
207
            total           => '1.000000',
208
            debit_type      => { 'description' => 'Overdue fine' }
209
        }
210
    ];
211
212
    # Transaction 3
213
    my $refund1 = $builder->build_object(
214
        {
215
            class => 'Koha::Account::Lines',
216
            value => {
217
                register_id       => undef,
218
                amount            => '-0.50',
219
                amountoutstanding => '0.00',
220
                credit_type_code  => 'REFUND',
221
                debit_type_code   => undef,
222
                date              => \'NOW() - INTERVAL 3 MINUTE'
223
            },
224
        }
225
    );
226
    my $outgoing1 = $builder->build_object(
227
        {
228
            class => 'Koha::Account::Lines',
229
            value => {
230
                register_id       => $register->id,
231
                amount            => '0.50',
232
                amountoutstanding => '0.00',
233
                credit_type_code  => undef,
234
                debit_type_code   => 'PAYOUT',
235
                date              => \'NOW() - INTERVAL 3 MINUTE'
236
            },
237
        }
238
    );
239
    $builder->build_object(
240
        {
241
            class => 'Koha::Account::Offsets',
242
            value => {
243
                credit_id => $refund1->accountlines_id,
244
                debit_id  => $outgoing1->accountlines_id,
245
                amount    => '0.50',
246
                type      => 'Refund'
247
            },
248
        }
249
    );
250
    my $expected_outgoing = [
251
        {
252
            'total'       => '0.500000',
253
            'credit_type' => {
254
                'description' => 'A refund applied to a patrons fine'
255
            },
256
            'credit_type_code' => 'REFUND'
257
        }
258
    ];
259
260
    my $cashup1 =
261
      $register->add_cashup( { manager_id => $manager->id, amount => '2.00' } );
262
263
    my $summary = $cashup1->cashup_summary;
264
265
    is( $summary->{from_date}, undef,
266
        "from_date is undefined if there is only one recorded" );
267
    is( $summary->{to_date}, $cashup1->timestamp,
268
        "to_date equals cashup timestamp" );
269
    is( ref( $summary->{income_transactions} ),
270
        'Koha::Account::Lines',
271
        "income_transactions contains Koha::Account::Lines" );
272
    is( $summary->{income_transactions}->count,
273
        2, "income_transactions contains 2 transactions" );
274
    is( ref( $summary->{outgoing_transactions} ),
275
        'Koha::Account::Lines',
276
        "outgoing_transactions contains Koha::Account::Lines" );
277
    is( $summary->{outgoing_transactions}->count,
278
        1, "outgoing_transactions contains 1 transaction" );
279
    is_deeply( $summary->{income}, $expected_income,
280
        "income arrayref is correct" );
281
    is_deeply( $summary->{outgoing}, $expected_outgoing,
282
        "outgoing arrayref is correct" );
283
284
    # Backdate cashup1 so we can add a new cashup to check 'previous'
285
    $cashup1->timestamp(\'NOW() - INTERVAL 2 MINUTE')->store();
286
    $cashup1->discard_changes;
287
288
    # Transaction 4
289
    my $debt4 = $builder->build_object(
290
        {
291
            class => 'Koha::Account::Lines',
292
            value => {
293
                register_id       => undef,
294
                amount            => '2.75',
295
                amountoutstanding => '0.00',
296
                credit_type_code  => undef,
297
                debit_type_code   => 'OVERDUE',
298
                date              => \'NOW() - INTERVAL 1 MINUTE'
299
            },
300
        }
301
    );
302
    my $income3 = $builder->build_object(
303
        {
304
            class => 'Koha::Account::Lines',
305
            value => {
306
                register_id       => $register->id,
307
                amount            => '-2.75',
308
                amountoutstanding => '0.00',
309
                credit_type_code  => 'PAYMENT',
310
                debit_type_code   => undef,
311
                date              => \'NOW() - INTERVAL 1 MINUTE'
312
            },
313
        }
314
    );
315
    $builder->build_object(
316
        {
317
            class => 'Koha::Account::Offsets',
318
            value => {
319
                credit_id => $income3->accountlines_id,
320
                debit_id  => $debt4->accountlines_id,
321
                amount    => '2.75',
322
                type      => 'Payment'
323
            },
324
        }
325
    );
326
327
    my $cashup2 =
328
      $register->add_cashup( { manager_id => $manager->id, amount => '2.75' } );
329
330
    $summary = $cashup2->cashup_summary;
331
332
    is( $summary->{from_date}, $cashup1->timestamp,
333
        "from_date returns the timestamp of the previous cashup action" );
334
    is( $summary->{to_date}, $cashup2->timestamp,
335
        "to_date equals cashup timestamp" );
336
    is( ref( $summary->{income_transactions} ),
337
        'Koha::Account::Lines',
338
        "income_transactions contains Koha::Account::Lines" );
339
    is( $summary->{income_transactions}->count,
340
        1, "income_transactions contains 2 transactions" );
341
    is( ref( $summary->{outgoing_transactions} ),
342
        'Koha::Account::Lines',
343
        "outgoing_transactions contains Koha::Account::Lines" );
344
    is( $summary->{outgoing_transactions}->count,
345
        0, "outgoing_transactions contains 1 transaction" );
346
347
    $schema->storage->txn_rollback;
348
};
349
350
1;
83
1;
(-)a/t/db_dependent/Koha/Cash/Register/Cashup.t (-1 / +352 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# Copyright 2020 Koha Development team
4
#
5
# This file is part of Koha
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use Test::More tests => 3;
22
23
use Koha::Database;
24
25
use t::lib::TestBuilder;
26
27
my $builder = t::lib::TestBuilder->new;
28
my $schema  = Koha::Database->new->schema;
29
30
subtest 'manager' => sub {
31
    plan tests => 2;
32
33
    $schema->storage->txn_begin;
34
35
    my $manager = $builder->build_object( { class => 'Koha::Patrons' } );
36
    my $cashup = $builder->build_object(
37
        {
38
            class => 'Koha::Cash::Register::Cashups',
39
            value => { manager_id => $manager->borrowernumber },
40
        }
41
    );
42
43
    is( ref( $cashup->manager ),
44
        'Koha::Patron',
45
        'Koha::Cash::Register::Cashup->manager should return a Koha::Patron' );
46
47
    is( $cashup->manager->id, $manager->id,
48
        'Koha::Cash::Register::Cashup->manager returns the correct Koha::Patron'
49
    );
50
51
    $schema->storage->txn_rollback;
52
53
};
54
55
subtest 'register' => sub {
56
    plan tests => 2;
57
58
    $schema->storage->txn_begin;
59
60
    my $register =
61
      $builder->build_object( { class => 'Koha::Cash::Registers' } );
62
    my $cashup = $builder->build_object(
63
        {
64
            class => 'Koha::Cash::Register::Cashups',
65
            value => { register_id => $register->id },
66
        }
67
    );
68
69
    is(
70
        ref( $cashup->register ),
71
        'Koha::Cash::Register',
72
'Koha::Cash::Register::Cashup->register should return a Koha::Cash::Register'
73
    );
74
75
    is( $cashup->register->id, $register->id,
76
'Koha::Cash::Register::Cashup->register returns the correct Koha::Cash::Register'
77
    );
78
79
    $schema->storage->txn_rollback;
80
81
};
82
83
subtest 'summary' => sub {
84
    plan tests => 29;
85
86
    $schema->storage->txn_begin;
87
88
    my $register =
89
      $builder->build_object( { class => 'Koha::Cash::Registers' } );
90
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
91
    my $manager = $builder->build_object( { class => 'Koha::Patrons' } );
92
    my $account = $patron->account;
93
    my $expected_total   = 0;
94
    my $expected_income_total   = 0;
95
    my $expected_income_grouped = [];
96
    my $expected_payout_total   = 0;
97
    my $expected_payout_grouped = [];
98
99
    # Transaction 1 (Fine (1.00) + Payment (-1.00))
100
    my $fine1 = $account->add_debit(
101
        {
102
            amount    => '1.00',
103
            type      => 'OVERDUE',
104
            interface => 'cron'
105
        }
106
    );
107
    $fine1->date( \'NOW() - INTERVAL 20 MINUTE' )->store;
108
109
    my $payment1 = $account->pay(
110
        {
111
            cash_register => $register->id,
112
            amount        => '1.00',
113
            credit_type   => 'PAYMENT',
114
            lines         => [$fine1]
115
        }
116
    );
117
    $payment1 = Koha::Account::Lines->find( $payment1->{payment_id} );
118
    $payment1->date( \'NOW() - INTERVAL 15 MINUTE' )->store;
119
    $expected_income_total += '1.00';
120
121
    # Overdue of 1.0 fully paid
122
    unshift @{$expected_income_grouped},
123
      {
124
        debit_type_code => 'OVERDUE',
125
        total           => '1',
126
        debit_type      => { description => 'Overdue fine' }
127
      };
128
129
    # Transaction 2 (Account (1.00) + Lost (0.50) + Payment (-1.50))
130
    my $account1 = $account->add_debit(
131
        {
132
            amount    => '1.00',
133
            type      => 'ACCOUNT',
134
            interface => 'cron'
135
        }
136
    );
137
    $account1->date( \'NOW() - INTERVAL 13 MINUTE' )->store;
138
    my $lost1 = $account->add_debit(
139
        {
140
            amount    => '0.50',
141
            type      => 'LOST',
142
            interface => 'cron'
143
        }
144
    );
145
    $lost1->date( \'NOW() - INTERVAL 13 MINUTE' )->store;
146
    my $payment2 = $account->pay(
147
        {
148
            cash_register => $register->id,
149
            amount        => '1.50',
150
            credit_type   => 'PAYMENT',
151
            lines         => [ $account1, $lost1 ]
152
        }
153
    );
154
    $payment2 = Koha::Account::Lines->find( $payment2->{payment_id} );
155
    $payment2->date( \'NOW() - INTERVAL 13 MINUTE' )->store;
156
    $expected_income_total += '1.5';
157
158
    # Lost charge of 0.5 fully paid
159
    unshift @{$expected_income_grouped},
160
      {
161
        debit_type_code => 'LOST',
162
        total           => '0.5',
163
        debit_type      => { description => 'Lost item' }
164
      };
165
166
    # Account fee of 1.0 fully paid
167
    unshift @{$expected_income_grouped},
168
      {
169
        debit_type_code => 'ACCOUNT',
170
        total           => '1',
171
        debit_type      => { description => 'Account creation fee' }
172
      };
173
174
    # Transaction 3 (Refund (-0.50) + Payout (0.50))
175
    $lost1->discard_changes;
176
    my $refund1 = $lost1->reduce(
177
        {
178
            amount         => '0.50',
179
            reduction_type => 'REFUND',
180
            interface      => 'cron'
181
        }
182
    );
183
    $refund1->date( \'NOW() - INTERVAL 13 MINUTE' )->store;
184
185
    my $payout1 = $refund1->payout(
186
        {
187
            cash_register => $register->id,
188
            amount        => '0.50',
189
            payout_type   => 'CASH',
190
            interface     => 'intranet',
191
            staff_id      => $manager->borrowernumber,
192
            branch        => $manager->branchcode
193
        }
194
    );
195
    $payout1->date( \'NOW() - INTERVAL 13 MINUTE' )->store;
196
    $expected_payout_total += '0.5';
197
198
    # Lost fee of 0.50 fully refunded
199
    unshift @{$expected_payout_grouped},
200
      {
201
        'total'       => '0.5',
202
        'credit_type' => {
203
            'description' => 'A refund applied to a patrons fine'
204
        },
205
        'credit_type_code' => 'REFUND'
206
      };
207
208
    $expected_total += $expected_income_total;
209
    $expected_total -= $expected_payout_total;
210
211
    diag("Cashup 1");
212
    my $cashup1 =
213
      $register->add_cashup( { manager_id => $manager->id, amount => '2.00' } );
214
215
    my $summary = $cashup1->summary;
216
217
    is( $summary->{from_date}, undef, "from_date is undefined if there is only one recorded" );
218
    is( $summary->{to_date}, $cashup1->timestamp, "to_date equals cashup timestamp" );
219
    is( ref( $summary->{income_grouped} ), 'ARRAY', "income_grouped contains an arrayref" );
220
    is( scalar @{ $summary->{income_grouped} }, 3, "income_grouped contains 3 transactions" );
221
    is_deeply( $summary->{income_grouped}, $expected_income_grouped, "income_grouped arrayref is correct" );
222
    is( $summary->{income_total}, $expected_income_total, "income_total is correct" );
223
224
    is( ref( $summary->{payout_grouped} ), 'ARRAY', "payout_grouped contains an arrayref" );
225
    is( scalar @{ $summary->{payout_grouped} }, 1, "payout_grouped contains 1 transaction" );
226
    is_deeply( $summary->{payout_grouped}, $expected_payout_grouped, "payout_grouped arrayref is correct" );
227
    is( $summary->{payout_total}, $expected_payout_total, "payout_total is correct" );
228
    is( $summary->{total}, $expected_total,"total equals expected_total" );
229
230
    # Backdate cashup1 so we can add a new cashup to check 'previous'
231
    $cashup1->timestamp( \'NOW() - INTERVAL 12 MINUTE' )->store();
232
    $cashup1->discard_changes;
233
    $expected_total = 0;
234
    $expected_income_total   = 0;
235
    $expected_income_grouped = [];
236
    $expected_payout_total   = 0;
237
    $expected_payout_grouped = [];
238
239
    # Transaction 4 ( Fine (2.75) + Partial payment (-2.00) )
240
    my $fine2 = $account->add_debit(
241
        {
242
            amount    => '2.75',
243
            type      => 'OVERDUE',
244
            interface => 'cron'
245
        }
246
    );
247
    $fine2->date( \'NOW() - INTERVAL 10 MINUTE' )->store;
248
249
    my $payment3 = $account->pay(
250
        {
251
            cash_register => $register->id,
252
            amount        => '2.00',
253
            credit_type   => 'PAYMENT',
254
            lines         => [$fine2]
255
        }
256
    );
257
    $payment3 = Koha::Account::Lines->find( $payment3->{payment_id} );
258
    $payment3->date( \'NOW() - INTERVAL 10 MINUTE' )->store;
259
    $expected_income_total += '2.00';
260
261
    unshift @{$expected_income_grouped},
262
      {
263
        debit_type_code => 'OVERDUE',
264
        total           => '-2.000000' * -1,
265
        debit_type      => { 'description' => 'Overdue fine' }
266
      };
267
268
    $expected_total += $expected_income_total;
269
    $expected_total -= $expected_payout_total;
270
271
    diag("Cashup 2");
272
    my $cashup2 =
273
      $register->add_cashup( { manager_id => $manager->id, amount => '2.00' } );
274
275
    $summary = $cashup2->summary;
276
277
    is( $summary->{from_date}, $cashup1->timestamp, "from_date returns the timestamp of the previous cashup cashup" );
278
    is( $summary->{to_date}, $cashup2->timestamp, "to_date equals cashup timestamp" );
279
    is( ref( $summary->{income_grouped} ), 'ARRAY', "income_grouped contains Koha::Account::Lines" );
280
    is( scalar @{ $summary->{income_grouped} }, 1, "income_grouped contains 1 transaction" );
281
    is_deeply( $summary->{income_grouped}, $expected_income_grouped, "income_grouped arrayref is correct for partial payment" );
282
    is( ref( $summary->{payout_grouped} ), 'ARRAY', "payout_grouped contains Koha::Account::Lines" );
283
    is( scalar @{ $summary->{payout_grouped} }, 0, "payout_grouped contains 0 transactions" );
284
    is_deeply( $summary->{payout_grouped}, $expected_payout_grouped, "payout_grouped arrayref is correct" );
285
    is( $summary->{total}, $expected_total, "total equals expected_total" );
286
287
    # Backdate cashup2 so we can add a new cashup to check
288
    $cashup2->timestamp( \'NOW() - INTERVAL 6 MINUTE' )->store();
289
    $cashup2->discard_changes;
290
    $expected_total = 0;
291
    $expected_income_total   = 0;
292
    $expected_income_grouped = [];
293
    $expected_payout_total   = 0;
294
    $expected_payout_grouped = [];
295
296
    # Transaction 5 (Refund (-1) + Payout (1))
297
    $account1->discard_changes;
298
    my $refund2 = $account1->reduce(
299
        {
300
            amount         => '1.00',
301
            reduction_type => 'REFUND',
302
            interface      => 'cron'
303
        }
304
    );
305
    $refund2->date( \'NOW() - INTERVAL 3 MINUTE' )->store;
306
307
    my $payout2 = $refund2->payout(
308
        {
309
            cash_register => $register->id,
310
            amount        => '1.00',
311
            payout_type   => 'CASH',
312
            interface     => 'intranet',
313
            staff_id      => $manager->borrowernumber,
314
            branch        => $manager->branchcode
315
        }
316
    );
317
    $payout2->date( \'NOW() - INTERVAL 3 MINUTE' )->store;
318
    $expected_payout_total += '1.00';
319
320
    # Account fee of 1.00 fully refunded (Accross cashup boundary)
321
    unshift @{$expected_payout_grouped},
322
      {
323
        'total'       => '1',
324
        'credit_type' => {
325
            'description' => 'A refund applied to a patrons fine'
326
        },
327
        'credit_type_code' => 'REFUND'
328
      };
329
330
    $expected_total += $expected_income_total;
331
    $expected_total -= $expected_payout_total;
332
333
    diag("Cashup 3");
334
    my $cashup3 =
335
      $register->add_cashup( { manager_id => $manager->id, amount => '2.00' } );
336
337
    $summary = $cashup3->summary;
338
339
    is( $summary->{from_date}, $cashup2->timestamp, "from_date returns the timestamp of the previous cashup cashup" );
340
    is( $summary->{to_date}, $cashup3->timestamp, "to_date equals cashup timestamp" );
341
    is( ref( $summary->{income_grouped} ), 'ARRAY', "income_grouped contains Koha::Account::Lines" );
342
    is( scalar @{ $summary->{income_grouped} }, 0, "income_grouped contains 1 transaction" );
343
    is_deeply( $summary->{income_grouped}, $expected_income_grouped, "income_grouped arrayref is correct for partial payment" );
344
    is( ref( $summary->{payout_grouped} ), 'ARRAY', "payout_grouped contains Koha::Account::Lines" );
345
    is( scalar @{ $summary->{payout_grouped} }, 1, "payout_grouped contains 0 transactions" );
346
    is_deeply( $summary->{payout_grouped}, $expected_payout_grouped, "payout_grouped arrayref is correct" );
347
    is( $summary->{total}, $expected_total, "total equals expected_total" );
348
349
    $schema->storage->txn_rollback;
350
};
351
352
1;

Return to bug 26274