| Lines 34-55
          
      
      
        Link Here | 
        
          | 34 | # dates should be in the format you have set up Koha to expect | 34 | # dates should be in the format you have set up Koha to expect | 
        
          | 35 | # branchcode and categorycode need to be valid | 35 | # branchcode and categorycode need to be valid | 
        
          | 36 |  | 36 |  | 
          
            
              | 37 | use strict; | 37 | use Modern::Perl; | 
            
              | 38 | use warnings; |  |  | 
        
          | 39 |  | 38 |  | 
        
          | 40 | use C4::Auth; | 39 | use C4::Auth; | 
        
          | 41 | use C4::Output; | 40 | use C4::Output; | 
            
              | 42 | use C4::Context; |  |  | 
            
              | 43 | use C4::Branch qw/GetBranchesLoop GetBranchName/; | 
            
              | 44 | use C4::Members; | 
            
              | 45 | use C4::Members::Attributes qw(:all); | 
            
              | 46 | use C4::Members::AttributeTypes; | 
            
              | 47 | use C4::Members::Messaging; | 
            
              | 48 | use C4::Reports::Guided; | 
        
          | 49 | use C4::Templates; | 41 | use C4::Templates; | 
        
          | 50 | use Koha::Patron::Debarments; | 42 | use Koha::Patron::Debarments; | 
        
          | 51 | use Koha::DateUtils; | 43 | use Koha::DateUtils; | 
        
          | 52 |  | 44 |  | 
            
              |  |  | 45 | use C4::Branch; | 
            
              | 46 | use C4::Members; | 
            
              | 47 |  | 
            
              | 48 | use Koha::Patrons::Import qw(import_patrons); | 
            
              | 49 |  | 
        
          | 53 | use Text::CSV; | 50 | use Text::CSV; | 
        
          | 54 |  | 51 |  | 
        
          | 55 | # Text::CSV::Unicode, even in binary mode, fails to parse lines with these diacriticals: | 52 | # Text::CSV::Unicode, even in binary mode, fails to parse lines with these diacriticals: | 
  
    | Lines 61-76
          use CGI qw ( -utf8 );
      
      
        Link Here | 
        
          | 61 | # use encoding 'utf8';    # don't do this | 58 | # use encoding 'utf8';    # don't do this | 
        
          | 62 |  | 59 |  | 
        
          | 63 | my ( @errors, @feedback ); | 60 | my ( @errors, @feedback ); | 
          
            
              | 64 | my $extended            = C4::Context->preference('ExtendedPatronAttributes'); | 61 | my $extended = C4::Context->preference('ExtendedPatronAttributes'); | 
            
              | 65 | my $set_messaging_prefs = C4::Context->preference('EnhancedMessagingPreferences'); | 62 |  | 
            
              | 66 | my @columnkeys          = C4::Members::columns(); | 63 | my @columnkeys = map { $_ ne 'borrowernumber' ? $_ : () } C4::Members::columns(); | 
            
              | 67 | @columnkeys = map { $_ ne 'borrowernumber' ? $_ : () } @columnkeys; | 64 | push( @columnkeys, 'patron_attributes' ) if $extended; | 
            
              | 68 | if ($extended) { |  |  | 
            
              | 69 |     push @columnkeys, 'patron_attributes'; | 
            
              | 70 | } | 
        
          | 71 |  | 65 |  | 
        
          | 72 | my $input = CGI->new(); | 66 | my $input = CGI->new(); | 
            
              | 73 | our $csv = Text::CSV->new( { binary => 1 } );    # binary needed for non-ASCII Unicode |  |  | 
        
          | 74 |  | 67 |  | 
        
          | 75 | #push @feedback, {feedback=>1, name=>'backend', value=>$csv->backend, backend=>$csv->backend}; #XXX | 68 | #push @feedback, {feedback=>1, name=>'backend', value=>$csv->backend, backend=>$csv->backend}; #XXX | 
        
          | 76 |  | 69 |  | 
  
    | Lines 97-102
          $columns = [ grep { $_->{field} ne 'borrowernumber' ? $_ : () } @$columns ];
      
      
        Link Here | 
        
          | 97 | $template->param( borrower_fields => $columns ); | 90 | $template->param( borrower_fields => $columns ); | 
        
          | 98 |  | 91 |  | 
        
          | 99 | if ( $input->param('sample') ) { | 92 | if ( $input->param('sample') ) { | 
            
              |  |  | 93 |     our $csv = Text::CSV->new( { binary => 1 } );    # binary needed for non-ASCII Unicode | 
        
          | 100 |     print $input->header( | 94 |     print $input->header( | 
        
          | 101 |         -type       => 'application/vnd.sun.xml.calc',    # 'application/vnd.ms-excel' ? | 95 |         -type       => 'application/vnd.sun.xml.calc',    # 'application/vnd.ms-excel' ? | 
        
          | 102 |         -attachment => 'patron_import.csv', | 96 |         -attachment => 'patron_import.csv', | 
  
    | Lines 105-415
          if ( $input->param('sample') ) {
      
      
        Link Here | 
        
          | 105 |     print $csv->string, "\n"; | 99 |     print $csv->string, "\n"; | 
        
          | 106 |     exit 0; | 100 |     exit 0; | 
        
          | 107 | } | 101 | } | 
            
              |  |  | 102 |  | 
        
          | 108 | my $uploadborrowers = $input->param('uploadborrowers'); | 103 | my $uploadborrowers = $input->param('uploadborrowers'); | 
        
          | 109 | my $matchpoint      = $input->param('matchpoint'); | 104 | my $matchpoint      = $input->param('matchpoint'); | 
        
          | 110 | if ($matchpoint) { | 105 | if ($matchpoint) { | 
        
          | 111 |     $matchpoint =~ s/^patron_attribute_//; | 106 |     $matchpoint =~ s/^patron_attribute_//; | 
        
          | 112 | } | 107 | } | 
            
              | 113 | my $overwrite_cardnumber = $input->param('overwrite_cardnumber'); |  |  | 
        
          | 114 |  | 108 |  | 
        
          | 115 | $template->param( SCRIPT_NAME => '/cgi-bin/koha/tools/import_borrowers.pl' ); | 109 | $template->param( SCRIPT_NAME => '/cgi-bin/koha/tools/import_borrowers.pl' ); | 
        
          | 116 |  | 110 |  | 
        
          | 117 | if ( $uploadborrowers && length($uploadborrowers) > 0 ) { | 111 | if ( $uploadborrowers && length($uploadborrowers) > 0 ) { | 
          
            
              | 118 |     push @feedback, { feedback => 1, name => 'filename', value => $uploadborrowers, filename => $uploadborrowers }; | 112 |     my $handle   = $input->upload('uploadborrowers'); | 
            
              | 119 |     my $handle     = $input->upload('uploadborrowers'); |  |  | 
            
              | 120 |     my $uploadinfo = $input->uploadInfo($uploadborrowers); | 
            
              | 121 |     foreach ( keys %$uploadinfo ) { | 
            
              | 122 |         push @feedback, { feedback => 1, name => $_, value => $uploadinfo->{$_}, $_ => $uploadinfo->{$_} }; | 
            
              | 123 |     } | 
            
              | 124 |     my $imported    = 0; | 
            
              | 125 |     my $alreadyindb = 0; | 
            
              | 126 |     my $overwritten = 0; | 
            
              | 127 |     my $invalid     = 0; | 
            
              | 128 |     my $matchpoint_attr_type; | 
        
          | 129 |     my %defaults = $input->Vars; | 113 |     my %defaults = $input->Vars; | 
        
          | 130 |  | 114 |  | 
          
            
              | 131 |     # use header line to construct key to column map | 115 |     my $return = Koha::Patrons::Import::import_patrons( | 
            
              | 132 |     my $borrowerline = <$handle>; | 116 |         { | 
            
              | 133 |     my $status       = $csv->parse($borrowerline); | 117 |             file                         => $handle, | 
            
              | 134 |     ($status) or push @errors, { badheader => 1, line => $., lineraw => $borrowerline }; | 118 |             defaults                     => \%defaults, | 
            
              | 135 |     my @csvcolumns = $csv->fields(); | 119 |             matchpoint                   => $matchpoint, | 
            
              | 136 |     my %csvkeycol; | 120 |             overwrite_cardnumber         => $input->param('overwrite_cardnumber'), | 
            
              | 137 |     my $col = 0; | 121 |             preserve_extended_attributes => $input->param('ext_preserve') || 0, | 
            
              | 138 |     foreach my $keycol (@csvcolumns) { |  |  | 
            
              | 139 |  | 
            
              | 140 |         # columnkeys don't contain whitespace, but some stupid tools add it | 
            
              | 141 |         $keycol =~ s/ +//g; | 
            
              | 142 |         $csvkeycol{$keycol} = $col++; | 
            
              | 143 |     } | 
            
              | 144 |  | 
            
              | 145 |     #warn($borrowerline); | 
            
              | 146 |     my $ext_preserve = $input->param('ext_preserve') || 0; | 
            
              | 147 |     if ($extended) { | 
            
              | 148 |         $matchpoint_attr_type = C4::Members::AttributeTypes->fetch($matchpoint); | 
            
              | 149 |     } | 
            
              | 150 |  | 
            
              | 151 |     push @feedback, { feedback => 1, name => 'headerrow', value => join( ', ', @csvcolumns ) }; | 
            
              | 152 |     my $today_iso = output_pref( { dt => dt_from_string, dateonly => 1, dateformat => 'iso' } ); | 
            
              | 153 |     my @criticals = qw(surname branchcode categorycode);    # there probably should be others | 
            
              | 154 |     my @bad_dates;                                          # I've had a few. | 
            
              | 155 |   LINE: while ( my $borrowerline = <$handle> ) { | 
            
              | 156 |         my %borrower; | 
            
              | 157 |         my @missing_criticals; | 
            
              | 158 |         my $patron_attributes; | 
            
              | 159 |         my $status  = $csv->parse($borrowerline); | 
            
              | 160 |         my @columns = $csv->fields(); | 
            
              | 161 |         if ( !$status ) { | 
            
              | 162 |             push @missing_criticals, { badparse => 1, line => $., lineraw => $borrowerline }; | 
            
              | 163 |         } | 
            
              | 164 |         elsif ( @columns == @columnkeys ) { | 
            
              | 165 |             @borrower{@columnkeys} = @columns; | 
            
              | 166 |  | 
            
              | 167 |             # MJR: try to fill blanks gracefully by using default values | 
            
              | 168 |             foreach my $key (@columnkeys) { | 
            
              | 169 |                 if ( $borrower{$key} !~ /\S/ ) { | 
            
              | 170 |                     $borrower{$key} = $defaults{$key}; | 
            
              | 171 |                 } | 
            
              | 172 |             } | 
            
              | 173 |         } | 
            
              | 174 |         else { | 
            
              | 175 |             # MJR: try to recover gracefully by using default values | 
            
              | 176 |             foreach my $key (@columnkeys) { | 
            
              | 177 |                 if ( defined( $csvkeycol{$key} ) and $columns[ $csvkeycol{$key} ] =~ /\S/ ) { | 
            
              | 178 |                     $borrower{$key} = $columns[ $csvkeycol{$key} ]; | 
            
              | 179 |                 } | 
            
              | 180 |                 elsif ( $defaults{$key} ) { | 
            
              | 181 |                     $borrower{$key} = $defaults{$key}; | 
            
              | 182 |                 } | 
            
              | 183 |                 elsif ( scalar grep { $key eq $_ } @criticals ) { | 
            
              | 184 |  | 
            
              | 185 |                     # a critical field is undefined | 
            
              | 186 |                     push @missing_criticals, { key => $key, line => $., lineraw => $borrowerline }; | 
            
              | 187 |                 } | 
            
              | 188 |                 else { | 
            
              | 189 |                     $borrower{$key} = ''; | 
            
              | 190 |                 } | 
            
              | 191 |             } | 
            
              | 192 |         } | 
            
              | 193 |  | 
            
              | 194 |         #warn join(':',%borrower); | 
            
              | 195 |         if ( $borrower{categorycode} ) { | 
            
              | 196 |             push @missing_criticals, | 
            
              | 197 |               { | 
            
              | 198 |                 key          => 'categorycode', | 
            
              | 199 |                 line         => $., | 
            
              | 200 |                 lineraw      => $borrowerline, | 
            
              | 201 |                 value        => $borrower{categorycode}, | 
            
              | 202 |                 category_map => 1 | 
            
              | 203 |               } | 
            
              | 204 |               unless GetBorrowercategory( $borrower{categorycode} ); | 
            
              | 205 |         } | 
            
              | 206 |         else { | 
            
              | 207 |             push @missing_criticals, { key => 'categorycode', line => $., lineraw => $borrowerline }; | 
            
              | 208 |         } | 
            
              | 209 |         if ( $borrower{branchcode} ) { | 
            
              | 210 |             push @missing_criticals, | 
            
              | 211 |               { | 
            
              | 212 |                 key        => 'branchcode', | 
            
              | 213 |                 line       => $., | 
            
              | 214 |                 lineraw    => $borrowerline, | 
            
              | 215 |                 value      => $borrower{branchcode}, | 
            
              | 216 |                 branch_map => 1 | 
            
              | 217 |               } | 
            
              | 218 |               unless GetBranchName( $borrower{branchcode} ); | 
        
          | 219 |         } | 122 |         } | 
          
            
              | 220 |         else { | 123 |     ); | 
            
              | 221 |             push @missing_criticals, { key => 'branchcode', line => $., lineraw => $borrowerline }; |  |  | 
            
              | 222 |         } | 
            
              | 223 |         if (@missing_criticals) { | 
            
              | 224 |             foreach (@missing_criticals) { | 
            
              | 225 |                 $_->{borrowernumber} = $borrower{borrowernumber} || 'UNDEF'; | 
            
              | 226 |                 $_->{surname}        = $borrower{surname}        || 'UNDEF'; | 
            
              | 227 |             } | 
            
              | 228 |             $invalid++; | 
            
              | 229 |             ( 25 > scalar @errors ) and push @errors, { missing_criticals => \@missing_criticals }; | 
            
              | 230 |  | 
            
              | 231 |             # The first 25 errors are enough.  Keeping track of 30,000+ would destroy performance. | 
            
              | 232 |             next LINE; | 
            
              | 233 |         } | 
            
              | 234 |         if ($extended) { | 
            
              | 235 |             my $attr_str = $borrower{patron_attributes}; | 
            
              | 236 |             $attr_str =~ s/\xe2\x80\x9c/"/g;    # fixup double quotes in case we are passed smart quotes | 
            
              | 237 |             $attr_str =~ s/\xe2\x80\x9d/"/g; | 
            
              | 238 |             push @feedback, | 
            
              | 239 |               { feedback => 1, name => 'attribute string', value => $attr_str, filename => $uploadborrowers }; | 
            
              | 240 |             delete $borrower{patron_attributes} | 
            
              | 241 |               ;    # not really a field in borrowers, so we don't want to pass it to ModMember. | 
            
              | 242 |             $patron_attributes = extended_attributes_code_value_arrayref($attr_str); | 
            
              | 243 |         } | 
            
              | 244 |  | 
            
              | 245 |         # Popular spreadsheet applications make it difficult to force date outputs to be zero-padded, but we require it. | 
            
              | 246 |         foreach (qw(dateofbirth dateenrolled dateexpiry)) { | 
            
              | 247 |             my $tempdate = $borrower{$_} or next; | 
            
              | 248 |             $tempdate = | 
            
              | 249 |               eval { output_pref( { dt => dt_from_string($tempdate), dateonly => 1, dateformat => 'iso' } ); }; | 
            
              | 250 |             if ($tempdate) { | 
            
              | 251 |                 $borrower{$_} = $tempdate; | 
            
              | 252 |             } | 
            
              | 253 |             else { | 
            
              | 254 |                 $borrower{$_} = ''; | 
            
              | 255 |                 push @missing_criticals, { key => $_, line => $., lineraw => $borrowerline, bad_date => 1 }; | 
            
              | 256 |             } | 
            
              | 257 |         } | 
            
              | 258 |         $borrower{dateenrolled} = $today_iso unless $borrower{dateenrolled}; | 
            
              | 259 |         $borrower{dateexpiry} = GetExpiryDate( $borrower{categorycode}, $borrower{dateenrolled} ) | 
            
              | 260 |           unless $borrower{dateexpiry}; | 
            
              | 261 |         my $borrowernumber; | 
            
              | 262 |         my $member; | 
            
              | 263 |         if ( ( $matchpoint eq 'cardnumber' ) && ( $borrower{'cardnumber'} ) ) { | 
            
              | 264 |             $member = GetMember( 'cardnumber' => $borrower{'cardnumber'} ); | 
            
              | 265 |             if ($member) { | 
            
              | 266 |                 $borrowernumber = $member->{'borrowernumber'}; | 
            
              | 267 |             } | 
            
              | 268 |         } | 
            
              | 269 |         elsif ( ( $matchpoint eq 'userid' ) && ( $borrower{'userid'} ) ) { | 
            
              | 270 |             $member = GetMember( 'userid' => $borrower{'userid'} ); | 
            
              | 271 |             if ($member) { | 
            
              | 272 |                 $borrowernumber = $member->{'borrowernumber'}; | 
            
              | 273 |             } | 
            
              | 274 |         } | 
            
              | 275 |         elsif ($extended) { | 
            
              | 276 |             if ( defined($matchpoint_attr_type) ) { | 
            
              | 277 |                 foreach my $attr (@$patron_attributes) { | 
            
              | 278 |                     if ( $attr->{code} eq $matchpoint and $attr->{value} ne '' ) { | 
            
              | 279 |                         my @borrowernumbers = $matchpoint_attr_type->get_patrons( $attr->{value} ); | 
            
              | 280 |                         $borrowernumber = $borrowernumbers[0] if scalar(@borrowernumbers) == 1; | 
            
              | 281 |                         last; | 
            
              | 282 |                     } | 
            
              | 283 |                 } | 
            
              | 284 |             } | 
            
              | 285 |         } | 
            
              | 286 |  | 
            
              | 287 |         if ( C4::Members::checkcardnumber( $borrower{cardnumber}, $borrowernumber ) ) { | 
            
              | 288 |             push @errors, | 
            
              | 289 |               { | 
            
              | 290 |                 invalid_cardnumber => 1, | 
            
              | 291 |                 borrowernumber     => $borrowernumber, | 
            
              | 292 |                 cardnumber         => $borrower{cardnumber} | 
            
              | 293 |               }; | 
            
              | 294 |             $invalid++; | 
            
              | 295 |             next; | 
            
              | 296 |         } | 
            
              | 297 |  | 
            
              | 298 |         if ($borrowernumber) { | 
            
              | 299 |  | 
            
              | 300 |             # borrower exists | 
            
              | 301 |             unless ($overwrite_cardnumber) { | 
            
              | 302 |                 $alreadyindb++; | 
            
              | 303 |                 $template->param( 'lastalreadyindb' => $borrower{'surname'} . ' / ' . $borrowernumber ); | 
            
              | 304 |                 next LINE; | 
            
              | 305 |             } | 
            
              | 306 |             $borrower{'borrowernumber'} = $borrowernumber; | 
            
              | 307 |             for my $col ( keys %borrower ) { | 
            
              | 308 |  | 
            
              | 309 |                 # use values from extant patron unless our csv file includes this column or we provided a default. | 
            
              | 310 |                 # FIXME : You cannot update a field with a  perl-evaluated false value using the defaults. | 
            
              | 311 |  | 
            
              | 312 |                 # The password is always encrypted, skip it! | 
            
              | 313 |                 next if $col eq 'password'; | 
            
              | 314 |  | 
            
              | 315 |                 unless ( exists( $csvkeycol{$col} ) || $defaults{$col} ) { | 
            
              | 316 |                     $borrower{$col} = $member->{$col} if ( $member->{$col} ); | 
            
              | 317 |                 } | 
            
              | 318 |             } | 
            
              | 319 |             unless ( ModMember(%borrower) ) { | 
            
              | 320 |                 $invalid++; | 
            
              | 321 |  | 
            
              | 322 |                 # until we have better error trapping, we have no way of knowing why ModMember errored out... | 
            
              | 323 |                 push @errors, { unknown_error => 1 }; | 
            
              | 324 |                 $template->param( 'lastinvalid' => $borrower{'surname'} . ' / ' . $borrowernumber ); | 
            
              | 325 |                 next LINE; | 
            
              | 326 |             } | 
            
              | 327 |  | 
            
              | 328 |             # Don't add a new restriction if the existing 'combined' restriction matches this one | 
            
              | 329 |             if ( $borrower{debarred} && ( ( $borrower{debarred} ne $member->{debarred} ) || ( $borrower{debarredcomment} ne $member->{debarredcomment} ) ) ) { | 
            
              | 330 |                 # Check to see if this debarment already exists | 
            
              | 331 |                 my $debarrments = GetDebarments( | 
            
              | 332 |                     { | 
            
              | 333 |                         borrowernumber => $borrowernumber, | 
            
              | 334 |                         expiration     => $borrower{debarred}, | 
            
              | 335 |                         comment        => $borrower{debarredcomment} | 
            
              | 336 |                     } | 
            
              | 337 |                 ); | 
            
              | 338 |  | 
            
              | 339 |                 # If it doesn't, then add it! | 
            
              | 340 |                 unless (@$debarrments) { | 
            
              | 341 |                     AddDebarment( | 
            
              | 342 |                         { | 
            
              | 343 |                             borrowernumber => $borrowernumber, | 
            
              | 344 |                             expiration     => $borrower{debarred}, | 
            
              | 345 |                             comment        => $borrower{debarredcomment} | 
            
              | 346 |                         } | 
            
              | 347 |                     ); | 
            
              | 348 |                 } | 
            
              | 349 |             } | 
            
              | 350 |  | 
            
              | 351 |             if ($extended) { | 
            
              | 352 |                 if ($ext_preserve) { | 
            
              | 353 |                     my $old_attributes = GetBorrowerAttributes($borrowernumber); | 
            
              | 354 |                     $patron_attributes = extended_attributes_merge( $old_attributes, $patron_attributes ) | 
            
              | 355 |                       ;    #TODO: expose repeatable options in template | 
            
              | 356 |                 } | 
            
              | 357 |                 push @errors, { unknown_error => 1 } | 
            
              | 358 |                   unless SetBorrowerAttributes( $borrower{'borrowernumber'}, $patron_attributes ); | 
            
              | 359 |             } | 
            
              | 360 |             $overwritten++; | 
            
              | 361 |             $template->param( 'lastoverwritten' => $borrower{'surname'} . ' / ' . $borrowernumber ); | 
            
              | 362 |         } | 
            
              | 363 |         else { | 
            
              | 364 |             # FIXME: fixup_cardnumber says to lock table, but the web interface doesn't so this doesn't either. | 
            
              | 365 |             # At least this is closer to AddMember than in members/memberentry.pl | 
            
              | 366 |             if ( !$borrower{'cardnumber'} ) { | 
            
              | 367 |                 $borrower{'cardnumber'} = fixup_cardnumber(undef); | 
            
              | 368 |             } | 
            
              | 369 |             if ( $borrowernumber = AddMember(%borrower) ) { | 
        
          | 370 |  | 124 |  | 
          
            
              | 371 |                 if ( $borrower{debarred} ) { | 125 |     my $feedback    = $return->{feedback}; | 
            
              | 372 |                     AddDebarment( | 126 |     my $errors      = $return->{errors}; | 
            
              | 373 |                         { | 127 |     my $imported    = $return->{imported}; | 
            
              | 374 |                             borrowernumber => $borrowernumber, | 128 |     my $overwritten = $return->{overwritten}; | 
            
              | 375 |                             expiration     => $borrower{debarred}, | 129 |     my $alreadyindb = $return->{already_in_db}; | 
            
              | 376 |                             comment        => $borrower{debarredcomment} | 130 |     my $invalid     = $return->{invalid}; | 
            
              | 377 |                         } |  |  | 
            
              | 378 |                     ); | 
            
              | 379 |                 } | 
        
          | 380 |  | 131 |  | 
          
            
              | 381 |                 if ($extended) { | 132 |     my $uploadinfo = $input->uploadInfo($uploadborrowers); | 
            
              | 382 |                     SetBorrowerAttributes( $borrowernumber, $patron_attributes ); | 133 |     foreach ( keys %$uploadinfo ) { | 
            
              | 383 |                 } | 134 |         push @$feedback, { feedback => 1, name => $_, value => $uploadinfo->{$_}, $_ => $uploadinfo->{$_} }; | 
            
              |  |  | 135 |     } | 
        
          | 384 |  | 136 |  | 
          
            
              | 385 |                 if ($set_messaging_prefs) { | 137 |     push @$feedback, { feedback => 1, name => 'filename', value => $uploadborrowers, filename => $uploadborrowers }; | 
            
              | 386 |                     C4::Members::Messaging::SetMessagingPreferencesFromDefaults( |  |  | 
            
              | 387 |                         { | 
            
              | 388 |                             borrowernumber => $borrowernumber, | 
            
              | 389 |                             categorycode   => $borrower{categorycode} | 
            
              | 390 |                         } | 
            
              | 391 |                     ); | 
            
              | 392 |                 } | 
        
          | 393 |  | 138 |  | 
            
              | 394 |                 $imported++; |  |  | 
            
              | 395 |                 $template->param( 'lastimported' => $borrower{'surname'} . ' / ' . $borrowernumber ); | 
            
              | 396 |             } | 
            
              | 397 |             else { | 
            
              | 398 |                 $invalid++; | 
            
              | 399 |                 push @errors, { unknown_error => 1 }; | 
            
              | 400 |                 $template->param( 'lastinvalid' => $borrower{'surname'} . ' / AddMember' ); | 
            
              | 401 |             } | 
            
              | 402 |         } | 
            
              | 403 |     } | 
            
              | 404 |     (@errors)   and $template->param( ERRORS   => \@errors ); | 
            
              | 405 |     (@feedback) and $template->param( FEEDBACK => \@feedback ); | 
        
          | 406 |     $template->param( | 139 |     $template->param( | 
          
            
              | 407 |         'uploadborrowers' => 1, | 140 |         uploadborrowers => 1, | 
            
              | 408 |         'imported'        => $imported, | 141 |         errors          => $errors, | 
            
              | 409 |         'overwritten'     => $overwritten, | 142 |         feedback        => $feedback, | 
            
              | 410 |         'alreadyindb'     => $alreadyindb, | 143 |         imported        => $imported, | 
            
              | 411 |         'invalid'         => $invalid, | 144 |         overwritten     => $overwritten, | 
            
              | 412 |         'total'           => $imported + $alreadyindb + $invalid + $overwritten, | 145 |         alreadyindb     => $alreadyindb, | 
            
              |  |  | 146 |         invalid         => $invalid, | 
            
              | 147 |         total           => $imported + $alreadyindb + $invalid + $overwritten, | 
        
          | 413 |     ); | 148 |     ); | 
        
          | 414 |  | 149 |  | 
        
          | 415 | } | 150 | } | 
            
              | 416 | -  |  |  |