Bug 22547: Add tests for C4::Overdues->UpdateFine
[koha-equinox.git] / t / db_dependent / Overdues.t
1 #!/usr/bin/perl;
2
3 use Modern::Perl;
4 use Test::More tests => 17;
5
6 use C4::Context;
7 use Koha::Database;
8 use Koha::Libraries;
9
10 use t::lib::Mocks;
11 use t::lib::TestBuilder;
12
13 use_ok('C4::Overdues');
14 can_ok('C4::Overdues', 'GetOverdueMessageTransportTypes');
15 can_ok('C4::Overdues', 'GetBranchcodesWithOverdueRules');
16
17 my $schema = Koha::Database->new->schema;
18 my $builder = t::lib::TestBuilder->new;
19
20 $schema->storage->txn_begin;
21 my $dbh = C4::Context->dbh;
22
23 $dbh->do(q|DELETE FROM letter|);
24 $dbh->do(q|DELETE FROM message_queue|);
25 $dbh->do(q|DELETE FROM message_transport_types|);
26 $dbh->do(q|DELETE FROM overduerules|);
27 $dbh->do(q|DELETE FROM overduerules_transport_types|);
28
29 $dbh->do(q|
30     INSERT INTO message_transport_types( message_transport_type ) VALUES ('email'), ('phone'), ('print'), ('sms')
31 |);
32
33 $dbh->do(q|
34     INSERT INTO overduerules ( overduerules_id, branchcode, categorycode ) VALUES
35     (1, 'CPL', 'PT'),
36     (2, 'CPL', 'YA'),
37     (3, '', 'PT'),
38     (4, '', 'YA')
39 |);
40
41 $dbh->do(q|INSERT INTO overduerules_transport_types (overduerules_id, letternumber, message_transport_type) VALUES
42     (1, 1, 'email'),
43     (1, 2, 'sms'),
44     (1, 3, 'email'),
45     (2, 3, 'print'),
46     (3, 1, 'email'),
47     (3, 2, 'email'),
48     (3, 2, 'sms'),
49     (3, 3, 'sms'),
50     (3, 3, 'email'),
51     (3, 3, 'print'),
52     (4, 2, 'sms')
53 |);
54
55 my $mtts;
56
57 $mtts = C4::Overdues::GetOverdueMessageTransportTypes('CPL', 'PT');
58 is( $mtts, undef, 'GetOverdueMessageTransportTypes: returns undef if no letternumber given' );
59
60 $mtts = C4::Overdues::GetOverdueMessageTransportTypes('CPL', undef, 1);
61 is( $mtts, undef, 'GetOverdueMessageTransportTypes: returns undef if no categorycode given' );
62
63 $mtts = C4::Overdues::GetOverdueMessageTransportTypes('CPL');
64 is( $mtts, undef, 'GetOverdueMessageTransportTypes: returns undef if no letternumber and categorycode given' );
65
66 $mtts = C4::Overdues::GetOverdueMessageTransportTypes('CPL', 'PT', 1);
67 is_deeply( $mtts, ['email'], 'GetOverdueMessageTransportTypes: first overdue is by email for PT (CPL)' );
68
69 $mtts = C4::Overdues::GetOverdueMessageTransportTypes('CPL', 'PT', 2);
70 is_deeply( $mtts, ['sms'], 'GetOverdueMessageTransportTypes: second overdue is by sms for PT (CPL)' );
71
72 $mtts = C4::Overdues::GetOverdueMessageTransportTypes('CPL', 'PT', 3);
73 is_deeply( $mtts, ['email'], 'GetOverdueMessageTransportTypes: third overdue is by email for PT (CPL)' );
74
75 $mtts = C4::Overdues::GetOverdueMessageTransportTypes('', 'PT', 1);
76 is_deeply( $mtts, ['email'], 'GetOverdueMessageTransportTypes: first overdue is by email for PT (default)' );
77
78 $mtts = C4::Overdues::GetOverdueMessageTransportTypes('', 'PT', 2);
79 is_deeply( $mtts, ['email', 'sms'], 'GetOverdueMessageTransportTypes: second overdue is by email and sms for PT (default)' );
80
81 $mtts = C4::Overdues::GetOverdueMessageTransportTypes('', 'PT', 3);
82 is_deeply( $mtts, ['print', 'sms', 'email'], 'GetOverdueMessageTransportTypes: third overdue is by print, sms and email for PT (default). With print in first.' );
83
84 # Test GetBranchcodesWithOverdueRules
85 $dbh->do(q|DELETE FROM overduerules|);
86 $dbh->do(q|
87     INSERT INTO overduerules
88         ( branchcode,categorycode, delay1,letter1,debarred1, delay2,letter2,debarred2, delay3,letter3,debarred3 )
89         VALUES
90         ( '', '', 1, 'LETTER_CODE1', 1, 5, 'LETTER_CODE2', 1, 10, 'LETTER_CODE3', 1 )
91 |);
92
93 my @branchcodes = map { $_->branchcode } Koha::Libraries->search;
94
95 my @overdue_branches = C4::Overdues::GetBranchcodesWithOverdueRules();
96 is_deeply( [ sort @overdue_branches ], [ sort @branchcodes ], 'If a default rule exists, all branches should be returned' );
97
98 $dbh->do(q|
99     INSERT INTO overduerules
100         ( branchcode,categorycode, delay1,letter1,debarred1, delay2,letter2,debarred2, delay3,letter3,debarred3 )
101         VALUES
102         ( 'CPL', '', 1, 'LETTER_CODE1', 1, 5, 'LETTER_CODE2', 1, 10, 'LETTER_CODE3', 1 )
103 |);
104
105 @overdue_branches = C4::Overdues::GetBranchcodesWithOverdueRules();
106 is_deeply( [ sort @overdue_branches ], [ sort @branchcodes ], 'If a default rule exists and a specific rule exists, all branches should be returned' );
107
108 $dbh->do(q|DELETE FROM overduerules|);
109 $dbh->do(q|
110     INSERT INTO overduerules
111         ( branchcode,categorycode, delay1,letter1,debarred1, delay2,letter2,debarred2, delay3,letter3,debarred3 )
112         VALUES
113         ( 'CPL', '', 1, 'LETTER_CODE1', 1, 5, 'LETTER_CODE2', 1, 10, 'LETTER_CODE3', 1 )
114 |);
115
116 @overdue_branches = C4::Overdues::GetBranchcodesWithOverdueRules();
117 is_deeply( \@overdue_branches, ['CPL'] , 'If only a specific rule exist, only 1 branch should be returned' );
118
119 $dbh->do(q|DELETE FROM overduerules|);
120 $dbh->do(q|
121     INSERT INTO overduerules
122         ( branchcode,categorycode, delay1,letter1,debarred1, delay2,letter2,debarred2, delay3,letter3,debarred3 )
123         VALUES
124         ( 'CPL', '', 1, 'LETTER_CODE1_CPL', 1, 5, 'LETTER_CODE2_CPL', 1, 10, 'LETTER_CODE3_CPL', 1 ),
125         ( 'MPL', '', 1, 'LETTER_CODE1_MPL', 1, 5, 'LETTER_CODE2_MPL', 1, 10, 'LETTER_CODE3_MPL', 1 )
126 |);
127
128 @overdue_branches = C4::Overdues::GetBranchcodesWithOverdueRules();
129 is_deeply( \@overdue_branches, ['CPL', 'MPL'] , 'If only 2 specific rules exist, 2 branches should be returned' );
130
131 $schema->storage->txn_rollback;
132
133 subtest 'UpdateFine tests' => sub {
134
135     plan tests => 25;
136
137     $schema->storage->txn_begin;
138
139     t::lib::Mocks::mock_preference( 'MaxFine', '100' );
140
141     my $patron    = $builder->build_object( { class => 'Koha::Patrons' } );
142     my $item1     = $builder->build_sample_item();
143     my $item2     = $builder->build_sample_item();
144     my $checkout1 = $builder->build_object(
145         {
146             class => 'Koha::Checkouts',
147             value => { itemnumber => $item1->itemnumber }
148         }
149     );
150     my $checkout2 = $builder->build_object(
151         {
152             class => 'Koha::Checkouts',
153             value => { itemnumber => $item2->itemnumber }
154         }
155     );
156
157     # Try to add 0 amount fine
158     UpdateFine(
159         {
160             issue_id       => $checkout1->issue_id,
161             itemnumber     => $item1->itemnumber,
162             borrowernumber => $patron->borrowernumber,
163             amount         => '0',
164             due            => $checkout1->date_due
165         }
166     );
167
168     my $fines = Koha::Account::Lines->search(
169         { borrowernumber => $patron->borrowernumber } );
170     is( $fines->count, 0, "No fine added when amount is 0" );
171
172     # Add fine 1
173     UpdateFine(
174         {
175             issue_id       => $checkout1->issue_id,
176             itemnumber     => $item1->itemnumber,
177             borrowernumber => $patron->borrowernumber,
178             amount         => '50',
179             due            => $checkout1->date_due
180         }
181     );
182
183     $fines = Koha::Account::Lines->search(
184         { borrowernumber => $patron->borrowernumber } );
185     is( $fines->count, 1, "Fine added when amount is greater than 0" );
186     my $fine = $fines->next;
187     is( $fine->amount, '50.000000', "Fine amount correctly set to 50" );
188     is( $fine->issue_id, $checkout1->issue_id, "Fine is associated with the correct issue" );
189     is( $fine->itemnumber, $checkout1->itemnumber, "Fine is associated with the correct item" );
190
191     # Increase fine 1
192     UpdateFine(
193         {
194             issue_id       => $checkout1->issue_id,
195             itemnumber     => $item1->itemnumber,
196             borrowernumber => $patron->borrowernumber,
197             amount         => '80',
198             due            => $checkout1->date_due
199         }
200     );
201
202     $fines = Koha::Account::Lines->search(
203         { borrowernumber => $patron->borrowernumber } );
204     is( $fines->count, 1, "Existing fine updated" );
205     $fine = $fines->next;
206     is( $fine->amount, '80.000000', "Fine amount correctly updated to 80" );
207
208     # Add fine 2
209     UpdateFine(
210         {
211             issue_id       => $checkout2->issue_id,
212             itemnumber     => $item2->itemnumber,
213             borrowernumber => $patron->borrowernumber,
214             amount         => '30',
215             due            => $checkout2->date_due
216         }
217     );
218
219     $fines = Koha::Account::Lines->search(
220         { borrowernumber => $patron->borrowernumber },
221         { order_by       => { '-asc' => 'accountlines_id' } }
222     );
223     is( $fines->count,        2,    "New fine added for second checkout" );
224     $fine = $fines->next;
225     is( $fine->amount, '80.000000', "First fine amount unchanged" );
226     my $fine2 = $fines->next;
227     is( $fine2->amount, '20.000000', "Second fine capped at '20' by MaxFine" );
228     is( $fine2->issue_id, $checkout2->issue_id, "Second fine is associated with the correct issue" );
229     is( $fine2->itemnumber, $checkout2->itemnumber, "Second fine is associated with the correct item" );
230
231     # Partial pay fine 1
232     $fine->amountoutstanding('50')->store;
233     UpdateFine(
234         {
235             issue_id       => $checkout2->issue_id,
236             itemnumber     => $item2->itemnumber,
237             borrowernumber => $patron->borrowernumber,
238             amount         => '30',
239             due            => $checkout2->date_due
240         }
241     );
242
243     $fines = Koha::Account::Lines->search(
244         { borrowernumber => $patron->borrowernumber },
245         { order_by       => { '-asc' => 'accountlines_id' } }
246     );
247     is( $fines->count,        2,    "Still two fines after second checkout update" );
248     $fine = $fines->next;
249     is( $fine->amount, '80.000000', "First fine amount unchanged" );
250     $fine2 = $fines->next;
251     is( $fine2->amount, '30.000000', "Second fine increased after partial payment of first" );
252
253     # Fix fine 1, create third fine
254     $fine->accounttype('F')->store;
255     UpdateFine(
256         {
257             issue_id       => $checkout1->issue_id,
258             itemnumber     => $item1->itemnumber,
259             borrowernumber => $patron->borrowernumber,
260             amount         => '30',
261             due            => $checkout1->date_due
262         }
263     );
264
265     $fines = Koha::Account::Lines->search(
266         { borrowernumber => $patron->borrowernumber },
267         { order_by       => { '-asc' => 'accountlines_id' } }
268     );
269     is( $fines->count,        3,    "Third fine added for overdue renewal" );
270     $fine = $fines->next;
271     is( $fine->amount, '80.000000', "First fine amount unchanged" );
272     $fine2 = $fines->next;
273     is( $fine2->amount, '30.000000', "Second fine amount unchanged" );
274     my $fine3 = $fines->next;
275     is( $fine3->amount, '20.000000', "Third fine amount capped due to MaxFine" );
276     is( $fine3->issue_id, $checkout1->issue_id, "Third fine is associated with the correct issue" );
277     is( $fine3->itemnumber, $checkout1->itemnumber, "Third fine is associated with the correct item" );
278
279     # FIXME: Add test to check whether sundry/manual charges are included within MaxFine.
280     # FIXME: Add test to ensure other charges are not included within MaxFine.
281
282     # Disable MaxFine
283     t::lib::Mocks::mock_preference( 'MaxFine', '0' );
284     UpdateFine(
285         {
286             issue_id       => $checkout1->issue_id,
287             itemnumber     => $item1->itemnumber,
288             borrowernumber => $patron->borrowernumber,
289             amount         => '30',
290             due            => $checkout1->date_due
291         }
292     );
293
294     $fines = Koha::Account::Lines->search(
295         { borrowernumber => $patron->borrowernumber },
296         { order_by       => { '-asc' => 'accountlines_id' } }
297     );
298     is( $fines->count,        3,    "Still only three fines after MaxFine cap removed" );
299     $fine = $fines->next;
300     is( $fine->amount, '80.000000', "First fine amount unchanged" );
301     $fine2 = $fines->next;
302     is( $fine2->amount, '30.000000', "Second fine amount unchanged" );
303     $fine3 = $fines->next;
304     is( $fine3->amount, '30.000000', "Third fine increased now MaxFine cap is disabled" );
305
306     $schema->storage->txn_rollback;
307 };