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