Bug 19040: [QA Follow-up] Adjust embed_items parameter in showdiffmarc
[koha.git] / tools / import_borrowers.pl
index 78353c9..d0aa3eb 100755 (executable)
@@ -5,18 +5,18 @@
 #
 # This file is part of Koha.
 #
-# Koha is free software; you can redistribute it and/or modify it under the
-# terms of the GNU General Public License as published by the Free Software
-# Foundation; either version 2 of the License, or (at your option) any later
-# version.
+# Koha is free software; you can redistribute it and/or modify it
+# under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
 #
-# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
-# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
-# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+# Koha is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
 #
-# You should have received a copy of the GNU General Public License along
-# with Koha; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+# You should have received a copy of the GNU General Public License
+# along with Koha; if not, see <http://www.gnu.org/licenses>.
 
 # Script to take some borrowers data in a known format and load it into Koha
 #
@@ -27,7 +27,7 @@
 # alternate streetnumber, alternate streettype, alternate address line 1, alternate city,
 # alternate zipcode, alternate country, alternate email, alternate phone, date of birth, branchcode,
 # categorycode, enrollment date, expiry date, noaddress, lost, debarred, contact surname,
-# contact firstname, contact title, borrower notes, contact relationship, ethnicity, ethnicity notes
+# contact firstname, contact title, borrower notes, contact relationship
 # gender, username, opac note, contact note, password, sort one, sort two
 #
 # any fields except cardnumber can be blank but the number of fields must match
@@ -39,37 +39,43 @@ use warnings;
 
 use C4::Auth;
 use C4::Output;
-use C4::Dates qw(format_date_in_iso);
 use C4::Context;
-use C4::Branch qw(GetBranchName);
 use C4::Members;
 use C4::Members::Attributes qw(:all);
 use C4::Members::AttributeTypes;
 use C4::Members::Messaging;
+use C4::Reports::Guided;
+use C4::Templates;
+use Koha::Patron::Debarments;
+use Koha::Patrons;
+use Koha::DateUtils;
+use Koha::Token;
+use Koha::Libraries;
+use Koha::Patron::Categories;
+use Koha::List::Patron;
 
 use Text::CSV;
 # Text::CSV::Unicode, even in binary mode, fails to parse lines with these diacriticals:
 # ė
 # č
 
-use CGI;
-# use encoding 'utf8';    # don't do this
+use CGI qw ( -utf8 );
 
 my (@errors, @feedback);
 my $extended = C4::Context->preference('ExtendedPatronAttributes');
 my $set_messaging_prefs = C4::Context->preference('EnhancedMessagingPreferences');
-my @columnkeys = C4::Members->columns;
+my @columnkeys = Koha::Patrons->columns();
+@columnkeys = map { $_ ne 'borrowernumber' ? $_ : () } @columnkeys;
 if ($extended) {
     push @columnkeys, 'patron_attributes';
 }
-my $columnkeystpl = [ map { {'key' => $_} }  grep {$_ ne 'borrowernumber' } @columnkeys ];  # ref. to array of hashrefs.
 
 my $input = CGI->new();
 our $csv  = Text::CSV->new({binary => 1});  # binary needed for non-ASCII Unicode
 #push @feedback, {feedback=>1, name=>'backend', value=>$csv->backend, backend=>$csv->backend}; #XXX
 
 my ( $template, $loggedinuser, $cookie ) = get_template_and_user({
-        template_name   => "tools/import_borrowers.tmpl",
+        template_name   => "tools/import_borrowers.tt",
         query           => $input,
         type            => "intranet",
         authnotrequired => 0,
@@ -77,7 +83,12 @@ my ( $template, $loggedinuser, $cookie ) = get_template_and_user({
         debug           => 1,
 });
 
-$template->param(columnkeys => $columnkeystpl);
+# get the patron categories and pass them to the template
+my @patron_categories = Koha::Patron::Categories->search_limited({}, {order_by => ['description']});
+$template->param( categories => \@patron_categories );
+my $columns = C4::Templates::GetColumnDefs( $input )->{borrowers};
+$columns = [ grep { $_->{field} ne 'borrowernumber' ? $_ : () } @$columns ];
+$template->param( borrower_fields => $columns );
 
 if ($input->param('sample')) {
     print $input->header(
@@ -86,7 +97,7 @@ if ($input->param('sample')) {
     );
     $csv->combine(@columnkeys);
     print $csv->string, "\n";
-    exit 1;
+    exit 0;
 }
 my $uploadborrowers = $input->param('uploadborrowers');
 my $matchpoint      = $input->param('matchpoint');
@@ -95,18 +106,30 @@ if ($matchpoint) {
 }
 my $overwrite_cardnumber = $input->param('overwrite_cardnumber');
 
-$template->param( SCRIPT_NAME => $ENV{'SCRIPT_NAME'} );
+#create a patronlist
+my $createpatronlist = $input->param('createpatronlist') || 0;
+my $dt = dt_from_string();
+my $timestamp = $dt->ymd('-').' '.$dt->hms(':');
+my $patronlistname = $uploadborrowers . ' (' . $timestamp .')';
 
-($extended) and $template->param(ExtendedPatronAttributes => 1);
+$template->param( SCRIPT_NAME => '/cgi-bin/koha/tools/import_borrowers.pl' );
 
 if ( $uploadborrowers && length($uploadborrowers) > 0 ) {
+    die "Wrong CSRF token"
+        unless Koha::Token->new->check_csrf({
+            session_id => scalar $input->cookie('CGISESSID'),
+            token  => scalar $input->param('csrf_token'),
+        });
+
     push @feedback, {feedback=>1, name=>'filename', value=>$uploadborrowers, filename=>$uploadborrowers};
     my $handle = $input->upload('uploadborrowers');
     my $uploadinfo = $input->uploadInfo($uploadborrowers);
     foreach (keys %$uploadinfo) {
         push @feedback, {feedback=>1, name=>$_, value=>$uploadinfo->{$_}, $_=>$uploadinfo->{$_}};
     }
+
     my $imported    = 0;
+    my @imported_borrowers;
     my $alreadyindb = 0;
     my $overwritten = 0;
     my $invalid     = 0;
@@ -132,11 +155,9 @@ if ( $uploadborrowers && length($uploadborrowers) > 0 ) {
     }
 
     push @feedback, {feedback=>1, name=>'headerrow', value=>join(', ', @csvcolumns)};
-    my $today_iso = C4::Dates->new()->output('iso');
+    my $today = output_pref;
     my @criticals = qw(surname branchcode categorycode);    # there probably should be others
     my @bad_dates;  # I've had a few.
-    my $date_re = C4::Dates->new->regexp('syspref');
-    my  $iso_re = C4::Dates->new->regexp('iso');
     LINE: while ( my $borrowerline = <$handle> ) {
         my %borrower;
         my @missing_criticals;
@@ -171,13 +192,13 @@ if ( $uploadborrowers && length($uploadborrowers) > 0 ) {
         #warn join(':',%borrower);
         if ($borrower{categorycode}) {
             push @missing_criticals, {key=>'categorycode', line=>$. , lineraw=>$borrowerline, value=>$borrower{categorycode}, category_map=>1}
-                unless GetBorrowercategory($borrower{categorycode});
+                unless Koha::Patron::Categories->find($borrower{categorycode});
         } else {
             push @missing_criticals, {key=>'categorycode', line=>$. , lineraw=>$borrowerline};
         }
         if ($borrower{branchcode}) {
             push @missing_criticals, {key=>'branchcode', line=>$. , lineraw=>$borrowerline, value=>$borrower{branchcode}, branch_map=>1}
-                unless GetBranchName($borrower{branchcode});
+                unless Koha::Libraries->find($borrower{branchcode});
         } else {
             push @missing_criticals, {key=>'branchcode', line=>$. , lineraw=>$borrowerline};
         }
@@ -202,24 +223,22 @@ if ( $uploadborrowers && length($uploadborrowers) > 0 ) {
        # Popular spreadsheet applications make it difficult to force date outputs to be zero-padded, but we require it.
         foreach (qw(dateofbirth dateenrolled dateexpiry)) {
             my $tempdate = $borrower{$_} or next;
-            if ($tempdate =~ /$date_re/) {
-                $borrower{$_} = format_date_in_iso($tempdate);
-            } elsif ($tempdate =~ /$iso_re/) {
+            $tempdate = eval { output_pref( { dt => dt_from_string( $tempdate ), dateonly => 1, dateformat => 'iso' } ); };
+            if ($tempdate) {
                 $borrower{$_} = $tempdate;
             } else {
                 $borrower{$_} = '';
                 push @missing_criticals, {key=>$_, line=>$. , lineraw=>$borrowerline, bad_date=>1};
             }
         }
-       $borrower{dateenrolled} = $today_iso unless $borrower{dateenrolled};
-       $borrower{dateexpiry} = GetExpiryDate($borrower{categorycode},$borrower{dateenrolled}) unless $borrower{dateexpiry}; 
+        $borrower{dateenrolled} ||= $today;
+        $borrower{dateexpiry}   ||= Koha::Patron::Categories->find( $borrower{categorycode} )->get_expiry_date( $borrower{dateenrolled} );
         my $borrowernumber;
         my $member;
         if ( ($matchpoint eq 'cardnumber') && ($borrower{'cardnumber'}) ) {
-            $member = GetMember( 'cardnumber' => $borrower{'cardnumber'} );
-            if ($member) {
-                $borrowernumber = $member->{'borrowernumber'};
-            }
+            $member = Koha::Patrons->find( { cardnumber => $borrower{'cardnumber'} } );
+        } elsif ( ($matchpoint eq 'userid') && ($borrower{'userid'}) ) {
+            $member = Koha::Patrons->find( { userid => $borrower{'userid'} } );
         } elsif ($extended) {
             if (defined($matchpoint_attr_type)) {
                 foreach my $attr (@$patron_attributes) {
@@ -231,7 +250,24 @@ if ( $uploadborrowers && length($uploadborrowers) > 0 ) {
                 }
             }
         }
-            
+
+        if ($member) {
+            $member = $member->unblessed;
+            $borrowernumber = $member->{'borrowernumber'};
+        } else {
+            $member = {};
+        }
+
+        if ( C4::Members::checkcardnumber( $borrower{cardnumber}, $borrowernumber ) ) {
+            push @errors, {
+                invalid_cardnumber => 1,
+                borrowernumber => $borrowernumber,
+                cardnumber => $borrower{cardnumber}
+            };
+            $invalid++;
+            next;
+        }
+
         if ($borrowernumber) {
             # borrower exists
             unless ($overwrite_cardnumber) {
@@ -251,19 +287,52 @@ if ( $uploadborrowers && length($uploadborrowers) > 0 ) {
                     $borrower{$col} = $member->{$col} if($member->{$col}) ;
                 }
             }
+
+            # Check if the userid provided does not exist yet
+            if (  exists $borrower{userid}
+                     and $borrower{userid}
+                 and not Check_Userid( $borrower{userid}, $borrower{borrowernumber} ) ) {
+                push @errors, { duplicate_userid => 1, userid => $borrower{userid} };
+                $invalid++;
+                next LINE;
+            }
+
             unless (ModMember(%borrower)) {
                 $invalid++;
-                # untill we have better error trapping, we have no way of knowing why ModMember errored out...
+                # until we have better error trapping, we have no way of knowing why ModMember errored out...
                 push @errors, {unknown_error => 1};
                 $template->param('lastinvalid'=>$borrower{'surname'}.' / '.$borrowernumber);
                 next LINE;
             }
+
+            # Don't add a new restriction if the existing 'combined' restriction matches this one
+            if ( $borrower{debarred} && ( ( $borrower{debarred} ne $member->{debarred} ) || ( $borrower{debarredcomment} ne $member->{debarredcomment} ) ) ) {
+                # Check to see if this debarment already exists
+                my $debarrments = GetDebarments(
+                    {
+                        borrowernumber => $borrowernumber,
+                        expiration     => $borrower{debarred},
+                        comment        => $borrower{debarredcomment}
+                    }
+                );
+                # If it doesn't, then add it!
+                unless (@$debarrments) {
+                    AddDebarment(
+                        {
+                            borrowernumber => $borrowernumber,
+                            expiration     => $borrower{debarred},
+                            comment        => $borrower{debarredcomment}
+                        }
+                    );
+                }
+            }
+
             if ($extended) {
                 if ($ext_preserve) {
                     my $old_attributes = GetBorrowerAttributes($borrowernumber);
                     $patron_attributes = extended_attributes_merge($old_attributes, $patron_attributes);  #TODO: expose repeatable options in template
                 }
-                push @errors, {unknown_error => 1} unless SetBorrowerAttributes($borrower{'borrowernumber'}, $patron_attributes);
+                push @errors, {unknown_error => 1} unless SetBorrowerAttributes($borrower{'borrowernumber'}, $patron_attributes, 'no_branch_limit' );
             }
             $overwritten++;
             $template->param('lastoverwritten'=>$borrower{'surname'}.' / '.$borrowernumber);
@@ -274,15 +343,29 @@ if ( $uploadborrowers && length($uploadborrowers) > 0 ) {
                 $borrower{'cardnumber'} = fixup_cardnumber(undef);
             }
             if ($borrowernumber = AddMember(%borrower)) {
+
+                if ( $borrower{debarred} ) {
+                    AddDebarment(
+                        {
+                            borrowernumber => $borrowernumber,
+                            expiration     => $borrower{debarred},
+                            comment        => $borrower{debarredcomment}
+                        }
+                    );
+                }
+
                 if ($extended) {
                     SetBorrowerAttributes($borrowernumber, $patron_attributes);
                 }
+
                 if ($set_messaging_prefs) {
                     C4::Members::Messaging::SetMessagingPreferencesFromDefaults({ borrowernumber => $borrowernumber,
                                                                                   categorycode => $borrower{categorycode} });
                 }
+
                 $imported++;
                 $template->param('lastimported'=>$borrower{'surname'}.' / '.$borrowernumber);
+                push @imported_borrowers, $borrowernumber; #for patronlist
             } else {
                 $invalid++;
                 push @errors, {unknown_error => 1};
@@ -290,6 +373,13 @@ if ( $uploadborrowers && length($uploadborrowers) > 0 ) {
             }
         }
     }
+
+    if ( $imported && $createpatronlist ) {
+        my $patronlist = AddPatronList({ name => $patronlistname });
+        AddPatronsToList({ list => $patronlist, borrowernumbers => \@imported_borrowers });
+        $template->param('patronlistname' => $patronlistname);
+    }
+
     (@errors  ) and $template->param(  ERRORS=>\@errors  );
     (@feedback) and $template->param(FEEDBACK=>\@feedback);
     $template->param(
@@ -313,6 +403,13 @@ if ( $uploadborrowers && length($uploadborrowers) > 0 ) {
         }
         $template->param(matchpoints => \@matchpoints);
     }
+
+    $template->param(
+        csrf_token => Koha::Token->new->generate_csrf(
+            { session_id => scalar $input->cookie('CGISESSID'), }
+        ),
+    );
+
 }
 
 output_html_with_http_headers $input, $cookie, $template->output;