Bug 17600: Standardize our EXPORT_OK
[srvgit] / Koha / Account.pm
1 package Koha::Account;
2
3 # Copyright 2016 ByWater Solutions
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
22 use Carp;
23 use Data::Dumper qw( Dumper );
24 use Try::Tiny qw( catch try );
25
26 use C4::Circulation qw( ReturnLostItem CanBookBeRenewed AddRenewal );
27 use C4::Letters;
28 use C4::Log qw( logaction );
29 use C4::Stats qw( UpdateStats );
30 use C4::Overdues qw(GetFine);
31
32 use Koha::Patrons;
33 use Koha::Account::Lines;
34 use Koha::Account::Offsets;
35 use Koha::Account::DebitTypes;
36 use Koha::Exceptions;
37 use Koha::Exceptions::Account;
38
39 =head1 NAME
40
41 Koha::Accounts - Module for managing payments and fees for patrons
42
43 =cut
44
45 sub new {
46     my ( $class, $params ) = @_;
47
48     Carp::croak("No patron id passed in!") unless $params->{patron_id};
49
50     return bless( $params, $class );
51 }
52
53 =head2 pay
54
55 This method allows payments to be made against fees/fines
56
57 Koha::Account->new( { patron_id => $borrowernumber } )->pay(
58     {
59         amount      => $amount,
60         note        => $note,
61         description => $description,
62         library_id  => $branchcode,
63         lines       => $lines, # Arrayref of Koha::Account::Line objects to pay
64         credit_type => $type,  # credit_type_code code
65         offset_type => $offset_type,    # offset type code
66         item_id     => $itemnumber,     # pass the itemnumber if this is a credit pertianing to a specific item (i.e LOST_FOUND)
67     }
68 );
69
70 =cut
71
72 sub pay {
73     my ( $self, $params ) = @_;
74
75     my $amount        = $params->{amount};
76     my $description   = $params->{description};
77     my $note          = $params->{note} || q{};
78     my $library_id    = $params->{library_id};
79     my $lines         = $params->{lines};
80     my $type          = $params->{type} || 'PAYMENT';
81     my $payment_type  = $params->{payment_type} || undef;
82     my $cash_register = $params->{cash_register};
83     my $item_id       = $params->{item_id};
84
85     my $userenv = C4::Context->userenv;
86
87     my $manager_id = $userenv ? $userenv->{number} : undef;
88     my $interface = $params ? ( $params->{interface} || C4::Context->interface ) : C4::Context->interface;
89     my $payment = $self->payin_amount(
90         {
91             interface     => $interface,
92             type          => $type,
93             amount        => $amount,
94             payment_type  => $payment_type,
95             cash_register => $cash_register,
96             user_id       => $manager_id,
97             library_id    => $library_id,
98             item_id       => $item_id,
99             description   => $description,
100             note          => $note,
101             debits        => $lines
102         }
103     );
104
105     # NOTE: Pay historically always applied as much credit as it could to all
106     # existing outstanding debits, whether passed specific debits or otherwise.
107     if ( $payment->amountoutstanding ) {
108         $payment =
109           $payment->apply(
110             { debits => [ $self->outstanding_debits->as_list ] } );
111     }
112
113     my $patron = Koha::Patrons->find( $self->{patron_id} );
114     my @account_offsets = $payment->debit_offsets;
115     if ( C4::Context->preference('UseEmailReceipts') ) {
116         if (
117             my $letter = C4::Letters::GetPreparedLetter(
118                 module                 => 'circulation',
119                 letter_code            => uc("ACCOUNT_$type"),
120                 message_transport_type => 'email',
121                 lang    => $patron->lang,
122                 tables => {
123                     borrowers       => $self->{patron_id},
124                     branches        => $library_id,
125                 },
126                 substitute => {
127                     credit => $payment,
128                     offsets => \@account_offsets,
129                 },
130               )
131           )
132         {
133             C4::Letters::EnqueueLetter(
134                 {
135                     letter                 => $letter,
136                     borrowernumber         => $self->{patron_id},
137                     message_transport_type => 'email',
138                 }
139             ) or warn "can't enqueue letter $letter";
140         }
141     }
142
143     my $renew_outcomes = [];
144     for my $message ( @{$payment->messages} ) {
145         push @{$renew_outcomes}, $message->payload;
146     }
147
148     return { payment_id => $payment->id, renew_result => $renew_outcomes };
149 }
150
151 =head3 add_credit
152
153 This method allows adding credits to a patron's account
154
155 my $credit_line = Koha::Account->new({ patron_id => $patron_id })->add_credit(
156     {
157         amount       => $amount,
158         description  => $description,
159         note         => $note,
160         user_id      => $user_id,
161         interface    => $interface,
162         library_id   => $library_id,
163         payment_type => $payment_type,
164         type         => $credit_type,
165         item_id      => $item_id
166     }
167 );
168
169 $credit_type can be any of:
170   - 'CREDIT'
171   - 'PAYMENT'
172   - 'FORGIVEN'
173   - 'LOST_FOUND'
174   - 'OVERPAYMENT'
175   - 'PAYMENT'
176   - 'WRITEOFF'
177
178 =cut
179
180 sub add_credit {
181
182     my ( $self, $params ) = @_;
183
184     # check for mandatory params
185     my @mandatory = ( 'interface', 'amount' );
186     for my $param (@mandatory) {
187         unless ( defined( $params->{$param} ) ) {
188             Koha::Exceptions::MissingParameter->throw(
189                 error => "The $param parameter is mandatory" );
190         }
191     }
192
193     # amount should always be passed as a positive value
194     my $amount = $params->{amount} * -1;
195     unless ( $amount < 0 ) {
196         Koha::Exceptions::Account::AmountNotPositive->throw(
197             error => 'Debit amount passed is not positive' );
198     }
199
200     my $description   = $params->{description} // q{};
201     my $note          = $params->{note} // q{};
202     my $user_id       = $params->{user_id};
203     my $interface     = $params->{interface};
204     my $library_id    = $params->{library_id};
205     my $cash_register = $params->{cash_register};
206     my $payment_type  = $params->{payment_type};
207     my $credit_type   = $params->{type} || 'PAYMENT';
208     my $item_id       = $params->{item_id};
209
210     Koha::Exceptions::Account::RegisterRequired->throw()
211       if ( C4::Context->preference("UseCashRegisters")
212         && defined($payment_type)
213         && ( $payment_type eq 'CASH' )
214         && !defined($cash_register) );
215
216     my $line;
217     my $schema = Koha::Database->new->schema;
218     try {
219         $schema->txn_do(
220             sub {
221
222                 # Insert the account line
223                 $line = Koha::Account::Line->new(
224                     {
225                         borrowernumber    => $self->{patron_id},
226                         date              => \'NOW()',
227                         amount            => $amount,
228                         description       => $description,
229                         credit_type_code  => $credit_type,
230                         amountoutstanding => $amount,
231                         payment_type      => $payment_type,
232                         note              => $note,
233                         manager_id        => $user_id,
234                         interface         => $interface,
235                         branchcode        => $library_id,
236                         register_id       => $cash_register,
237                         itemnumber        => $item_id,
238                     }
239                 )->store();
240
241                 # Record the account offset
242                 my $account_offset = Koha::Account::Offset->new(
243                     {
244                         credit_id => $line->id,
245                         type   => $Koha::Account::offset_type->{$credit_type} // $Koha::Account::offset_type->{CREDIT},
246                         amount => $amount
247                     }
248                 )->store();
249
250                 C4::Stats::UpdateStats(
251                     {
252                         branch         => $library_id,
253                         type           => lc($credit_type),
254                         amount         => $amount,
255                         borrowernumber => $self->{patron_id},
256                     }
257                 ) if grep { $credit_type eq $_ } ( 'PAYMENT', 'WRITEOFF' );
258
259                 if ( C4::Context->preference("FinesLog") ) {
260                     logaction(
261                         "FINES", 'CREATE',
262                         $self->{patron_id},
263                         Dumper(
264                             {
265                                 action            => "create_$credit_type",
266                                 borrowernumber    => $self->{patron_id},
267                                 amount            => $amount,
268                                 description       => $description,
269                                 amountoutstanding => $amount,
270                                 credit_type_code  => $credit_type,
271                                 note              => $note,
272                                 itemnumber        => $item_id,
273                                 manager_id        => $user_id,
274                                 branchcode        => $library_id,
275                             }
276                         ),
277                         $interface
278                     );
279                 }
280             }
281         );
282     }
283     catch {
284         if ( ref($_) eq 'Koha::Exceptions::Object::FKConstraint' ) {
285             if ( $_->broken_fk eq 'credit_type_code' ) {
286                 Koha::Exceptions::Account::UnrecognisedType->throw(
287                     error => 'Type of credit not recognised' );
288             }
289             else {
290                 $_->rethrow;
291             }
292         }
293     };
294
295     return $line;
296 }
297
298 =head3 payin_amount
299
300     my $credit = $account->payin_amount(
301         {
302             amount          => $amount,
303             type            => $credit_type,
304             payment_type    => $payment_type,
305             cash_register   => $register_id,
306             interface       => $interface,
307             library_id      => $branchcode,
308             user_id         => $staff_id,
309             debits          => $debit_lines,
310             description     => $description,
311             note            => $note
312         }
313     );
314
315 This method allows an amount to be paid into a patrons account and immediately applied against debts.
316
317 You can optionally pass a debts parameter which consists of an arrayref of Koha::Account::Line debit lines.
318
319 $credit_type can be any of:
320   - 'PAYMENT'
321   - 'WRITEOFF'
322   - 'FORGIVEN'
323
324 =cut
325
326 sub payin_amount {
327     my ( $self, $params ) = @_;
328
329     # check for mandatory params
330     my @mandatory = ( 'interface', 'amount', 'type' );
331     for my $param (@mandatory) {
332         unless ( defined( $params->{$param} ) ) {
333             Koha::Exceptions::MissingParameter->throw(
334                 error => "The $param parameter is mandatory" );
335         }
336     }
337
338     # Check for mandatory register
339     Koha::Exceptions::Account::RegisterRequired->throw()
340       if ( C4::Context->preference("UseCashRegisters")
341         && defined( $params->{payment_type} )
342         && ( $params->{payment_type} eq 'CASH' )
343         && !defined($params->{cash_register}) );
344
345     # amount should always be passed as a positive value
346     my $amount = $params->{amount};
347     unless ( $amount > 0 ) {
348         Koha::Exceptions::Account::AmountNotPositive->throw(
349             error => 'Payin amount passed is not positive' );
350     }
351
352     my $credit;
353     my $schema = Koha::Database->new->schema;
354     $schema->txn_do(
355         sub {
356
357             # Add payin credit
358             $credit = $self->add_credit($params);
359
360             # Offset debts passed first
361             if ( exists( $params->{debits} ) ) {
362                 $credit = $credit->apply(
363                     {
364                         debits      => $params->{debits},
365                         offset_type => $Koha::Account::offset_type->{$params->{type}}
366                     }
367                 );
368             }
369
370             # Offset against remaining balance if AutoReconcile
371             if ( C4::Context->preference("AccountAutoReconcile")
372                 && $credit->amountoutstanding != 0 )
373             {
374                 $credit = $credit->apply(
375                     {
376                         debits      => [ $self->outstanding_debits->as_list ],
377                         offset_type => $Koha::Account::offset_type->{$params->{type}}
378                     }
379                 );
380             }
381         }
382     );
383
384     return $credit;
385 }
386
387 =head3 add_debit
388
389 This method allows adding debits to a patron's account
390
391     my $debit_line = Koha::Account->new({ patron_id => $patron_id })->add_debit(
392         {
393             amount           => $amount,
394             description      => $description,
395             note             => $note,
396             user_id          => $user_id,
397             interface        => $interface,
398             library_id       => $library_id,
399             type             => $debit_type,
400             transaction_type => $transaction_type,
401             cash_register    => $register_id,
402             item_id          => $item_id,
403             issue_id         => $issue_id
404         }
405     );
406
407 $debit_type can be any of:
408   - ACCOUNT
409   - ACCOUNT_RENEW
410   - RESERVE_EXPIRED
411   - LOST
412   - sundry
413   - NEW_CARD
414   - OVERDUE
415   - PROCESSING
416   - RENT
417   - RENT_DAILY
418   - RENT_RENEW
419   - RENT_DAILY_RENEW
420   - RESERVE
421   - PAYOUT
422
423 =cut
424
425 sub add_debit {
426
427     my ( $self, $params ) = @_;
428
429     # check for mandatory params
430     my @mandatory = ( 'interface', 'type', 'amount' );
431     for my $param (@mandatory) {
432         unless ( defined( $params->{$param} ) ) {
433             Koha::Exceptions::MissingParameter->throw(
434                 error => "The $param parameter is mandatory" );
435         }
436     }
437
438     # check for cash register if using cash
439     Koha::Exceptions::Account::RegisterRequired->throw()
440       if ( C4::Context->preference("UseCashRegisters")
441         && defined( $params->{transaction_type} )
442         && ( $params->{transaction_type} eq 'CASH' )
443         && !defined( $params->{cash_register} ) );
444
445     # amount should always be a positive value
446     my $amount = $params->{amount};
447     unless ( $amount > 0 ) {
448         Koha::Exceptions::Account::AmountNotPositive->throw(
449             error => 'Debit amount passed is not positive' );
450     }
451
452     my $description      = $params->{description} // q{};
453     my $note             = $params->{note} // q{};
454     my $user_id          = $params->{user_id};
455     my $interface        = $params->{interface};
456     my $library_id       = $params->{library_id};
457     my $cash_register    = $params->{cash_register};
458     my $debit_type       = $params->{type};
459     my $transaction_type = $params->{transaction_type};
460     my $item_id          = $params->{item_id};
461     my $issue_id         = $params->{issue_id};
462     my $offset_type      = $Koha::Account::offset_type->{$debit_type} // 'Manual Debit';
463
464     my $line;
465     my $schema = Koha::Database->new->schema;
466     try {
467         $schema->txn_do(
468             sub {
469
470                 # Insert the account line
471                 $line = Koha::Account::Line->new(
472                     {
473                         borrowernumber    => $self->{patron_id},
474                         date              => \'NOW()',
475                         amount            => $amount,
476                         description       => $description,
477                         debit_type_code   => $debit_type,
478                         amountoutstanding => $amount,
479                         payment_type      => $transaction_type,
480                         note              => $note,
481                         manager_id        => $user_id,
482                         interface         => $interface,
483                         itemnumber        => $item_id,
484                         issue_id          => $issue_id,
485                         branchcode        => $library_id,
486                         register_id       => $cash_register,
487                         (
488                             $debit_type eq 'OVERDUE'
489                             ? ( status => 'UNRETURNED' )
490                             : ()
491                         ),
492                     }
493                 )->store();
494
495                 # Record the account offset
496                 my $account_offset = Koha::Account::Offset->new(
497                     {
498                         debit_id => $line->id,
499                         type     => $offset_type,
500                         amount   => $amount
501                     }
502                 )->store();
503
504                 if ( C4::Context->preference("FinesLog") ) {
505                     logaction(
506                         "FINES", 'CREATE',
507                         $self->{patron_id},
508                         Dumper(
509                             {
510                                 action            => "create_$debit_type",
511                                 borrowernumber    => $self->{patron_id},
512                                 amount            => $amount,
513                                 description       => $description,
514                                 amountoutstanding => $amount,
515                                 debit_type_code   => $debit_type,
516                                 note              => $note,
517                                 itemnumber        => $item_id,
518                                 manager_id        => $user_id,
519                             }
520                         ),
521                         $interface
522                     );
523                 }
524             }
525         );
526     }
527     catch {
528         if ( ref($_) eq 'Koha::Exceptions::Object::FKConstraint' ) {
529             if ( $_->broken_fk eq 'debit_type_code' ) {
530                 Koha::Exceptions::Account::UnrecognisedType->throw(
531                     error => 'Type of debit not recognised' );
532             }
533             else {
534                 $_->rethrow;
535             }
536         }
537     };
538
539     return $line;
540 }
541
542 =head3 payout_amount
543
544     my $debit = $account->payout_amount(
545         {
546             payout_type => $payout_type,
547             register_id => $register_id,
548             staff_id    => $staff_id,
549             interface   => 'intranet',
550             amount      => $amount,
551             credits     => $credit_lines
552         }
553     );
554
555 This method allows an amount to be paid out from a patrons account against outstanding credits.
556
557 $payout_type can be any of the defined payment_types:
558
559 =cut
560
561 sub payout_amount {
562     my ( $self, $params ) = @_;
563
564     # Check for mandatory parameters
565     my @mandatory =
566       ( 'interface', 'staff_id', 'branch', 'payout_type', 'amount' );
567     for my $param (@mandatory) {
568         unless ( defined( $params->{$param} ) ) {
569             Koha::Exceptions::MissingParameter->throw(
570                 error => "The $param parameter is mandatory" );
571         }
572     }
573
574     # Check for mandatory register
575     Koha::Exceptions::Account::RegisterRequired->throw()
576       if ( C4::Context->preference("UseCashRegisters")
577         && ( $params->{payout_type} eq 'CASH' )
578         && !defined($params->{cash_register}) );
579
580     # Amount should always be passed as a positive value
581     my $amount = $params->{amount};
582     unless ( $amount > 0 ) {
583         Koha::Exceptions::Account::AmountNotPositive->throw(
584             error => 'Payout amount passed is not positive' );
585     }
586
587     # Amount should always be less than or equal to outstanding credit
588     my $outstanding = 0;
589     my $outstanding_credits =
590       exists( $params->{credits} )
591       ? $params->{credits}
592       : $self->outstanding_credits->as_list;
593     for my $credit ( @{$outstanding_credits} ) {
594         $outstanding += $credit->amountoutstanding;
595     }
596     $outstanding = $outstanding * -1;
597     Koha::Exceptions::ParameterTooHigh->throw( error =>
598 "Amount to payout ($amount) is higher than amountoutstanding ($outstanding)"
599     ) unless ( $outstanding >= $amount );
600
601     my $payout;
602     my $schema = Koha::Database->new->schema;
603     $schema->txn_do(
604         sub {
605
606             # A 'payout' is a 'debit'
607             $payout = $self->add_debit(
608                 {
609                     amount            => $params->{amount},
610                     type              => 'PAYOUT',
611                     transaction_type  => $params->{payout_type},
612                     amountoutstanding => $params->{amount},
613                     manager_id        => $params->{staff_id},
614                     interface         => $params->{interface},
615                     branchcode        => $params->{branch},
616                     cash_register     => $params->{cash_register}
617                 }
618             );
619
620             # Offset against credits
621             for my $credit ( @{$outstanding_credits} ) {
622                 $credit->apply(
623                     { debits => [$payout], offset_type => 'PAYOUT' } );
624                 $payout->discard_changes;
625                 last if $payout->amountoutstanding == 0;
626             }
627
628             # Set payout as paid
629             $payout->status('PAID')->store;
630         }
631     );
632
633     return $payout;
634 }
635
636 =head3 balance
637
638 my $balance = $self->balance
639
640 Return the balance (sum of amountoutstanding columns)
641
642 =cut
643
644 sub balance {
645     my ($self) = @_;
646     return $self->lines->total_outstanding;
647 }
648
649 =head3 outstanding_debits
650
651 my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_debits;
652
653 It returns the debit lines with outstanding amounts for the patron.
654
655 In scalar context, it returns a Koha::Account::Lines iterator. In list context, it will
656 return a list of Koha::Account::Line objects.
657
658 =cut
659
660 sub outstanding_debits {
661     my ($self) = @_;
662
663     return $self->lines->search(
664         {
665             amount            => { '>' => 0 },
666             amountoutstanding => { '>' => 0 }
667         }
668     );
669 }
670
671 =head3 outstanding_credits
672
673 my $lines = Koha::Account->new({ patron_id => $patron_id })->outstanding_credits;
674
675 It returns the credit lines with outstanding amounts for the patron.
676
677 In scalar context, it returns a Koha::Account::Lines iterator. In list context, it will
678 return a list of Koha::Account::Line objects.
679
680 =cut
681
682 sub outstanding_credits {
683     my ($self) = @_;
684
685     return $self->lines->search(
686         {
687             amount            => { '<' => 0 },
688             amountoutstanding => { '<' => 0 }
689         }
690     );
691 }
692
693 =head3 non_issues_charges
694
695 my $non_issues_charges = $self->non_issues_charges
696
697 Calculates amount immediately owing by the patron - non-issue charges.
698
699 Charges exempt from non-issue are:
700 * Res (holds) if HoldsInNoissuesCharge syspref is set to false
701 * Rent (rental) if RentalsInNoissuesCharge syspref is set to false
702 * Manual invoices if ManInvInNoissuesCharge syspref is set to false
703
704 =cut
705
706 sub non_issues_charges {
707     my ($self) = @_;
708
709     #NOTE: With bug 23049 these preferences could be moved to being attached
710     #to individual debit types to give more flexability and specificity.
711     my @not_fines;
712     push @not_fines, 'RESERVE'
713       unless C4::Context->preference('HoldsInNoissuesCharge');
714     push @not_fines, ( 'RENT', 'RENT_DAILY', 'RENT_RENEW', 'RENT_DAILY_RENEW' )
715       unless C4::Context->preference('RentalsInNoissuesCharge');
716     unless ( C4::Context->preference('ManInvInNoissuesCharge') ) {
717         my @man_inv = Koha::Account::DebitTypes->search({ is_system => 0 })->get_column('code');
718         push @not_fines, @man_inv;
719     }
720
721     return $self->lines->search(
722         {
723             debit_type_code => { -not_in => \@not_fines }
724         },
725     )->total_outstanding;
726 }
727
728 =head3 lines
729
730 my $lines = $self->lines;
731
732 Return all credits and debits for the user, outstanding or otherwise
733
734 =cut
735
736 sub lines {
737     my ($self) = @_;
738
739     return Koha::Account::Lines->search(
740         {
741             borrowernumber => $self->{patron_id},
742         }
743     );
744 }
745
746 =head3 reconcile_balance
747
748 $account->reconcile_balance();
749
750 Find outstanding credits and use them to pay outstanding debits.
751 Currently, this implicitly uses the 'First In First Out' rule for
752 applying credits against debits.
753
754 =cut
755
756 sub reconcile_balance {
757     my ($self) = @_;
758
759     my $outstanding_debits  = $self->outstanding_debits;
760     my $outstanding_credits = $self->outstanding_credits;
761
762     while (     $outstanding_debits->total_outstanding > 0
763             and my $credit = $outstanding_credits->next )
764     {
765         # there's both outstanding debits and credits
766         $credit->apply( { debits => [ $outstanding_debits->as_list ] } );    # applying credit, no special offset
767
768         $outstanding_debits = $self->outstanding_debits;
769
770     }
771
772     return $self;
773 }
774
775 1;
776
777 =head2 Name mappings
778
779 =head3 $offset_type
780
781 =cut
782
783 our $offset_type = {
784     'CREDIT'           => 'Manual Credit',
785     'FORGIVEN'         => 'Writeoff',
786     'LOST_FOUND'       => 'Lost Item Found',
787     'OVERPAYMENT'      => 'Overpayment',
788     'PAYMENT'          => 'Payment',
789     'WRITEOFF'         => 'Writeoff',
790     'ACCOUNT'          => 'Account Fee',
791     'ACCOUNT_RENEW'    => 'Account Fee',
792     'RESERVE'          => 'Reserve Fee',
793     'PROCESSING'       => 'Processing Fee',
794     'LOST'             => 'Lost Item',
795     'RENT'             => 'Rental Fee',
796     'RENT_DAILY'       => 'Rental Fee',
797     'RENT_RENEW'       => 'Rental Fee',
798     'RENT_DAILY_RENEW' => 'Rental Fee',
799     'OVERDUE'          => 'OVERDUE',
800     'RESERVE_EXPIRED'  => 'Hold Expired',
801     'PAYOUT'           => 'PAYOUT',
802 };
803
804 =head1 AUTHORS
805
806 =encoding utf8
807
808 Kyle M Hall <kyle.m.hall@gmail.com>
809 Tomás Cohen Arazi <tomascohen@gmail.com>
810 Martin Renvoize <martin.renvoize@ptfs-europe.com>
811
812 =cut