final import parser for migration
[Evergreen.git] / Evergreen / src / extras / import / parse_patron_xml.pl
1 #!/usr/bin/perl
2 use strict;
3 use warnings;
4
5 use lib '/openils/lib/perl5';
6
7 use OpenSRF::System;
8 use OpenSRF::EX qw/:try/;
9 use OpenSRF::AppSession;
10 use OpenSRF::Utils::SettingsClient;
11 use OpenILS::Utils::Fieldmapper;
12 use Digest::MD5 qw/md5_hex/;
13 use Getopt::Long;
14 use JSON;
15 use DateTime;
16 use Time::HiRes qw/time/;
17 use XML::LibXML;
18
19 my ($file,$config,$profileid,$identtypeid,$default_profile,$profile_map,$seenmap,$nosaveseen,$usermap) =
20         ('return_file_0623-2.xml', '/openils/conf/bootstrap.conf', 1, 3, 'User', 'profile.map','/tmp/patron-import.seen');
21
22 GetOptions(
23         'usermap=s'        => \$usermap,
24         'file=s'        => \$file,
25         'config=s'      => \$config,
26         'seenmap=s'      => \$seenmap,
27         'no_save_seenmap'      => \$nosaveseen,
28         'default_profile=i'      => \$default_profile,
29         'profile_map=s'      => \$profile_map,
30         'profile_statcat_id=i'      => \$profileid,
31         'identtypeid=i'      => \$identtypeid,
32 );
33
34 my %u_map;
35 if ($usermap) {
36         open F, $usermap;
37         while (my $line = <F>) {
38                 chomp($line);
39                 my ($b,$i) = split(/\|/, $line);
40                 $b =~ s/^\s*(\S+)\s*$/$1/o;
41                 $i =~ s/^\s*(\S+)\s*$/$1/o;
42                 $u_map{$b} = $i;
43         }
44         close F;
45 }
46
47 my %p_map;
48 if ($profile_map) {
49         open F, $profile_map;
50         while (my $line = <F>) {
51                 chomp($line);
52                 my ($b,$i) = split(/\|/, $line);
53                 $b =~ s/^\s*(\S+)\s*$/$1/o;
54                 $i =~ s/^\s*(\S+)\s*$/$1/o;
55                 $p_map{$b} = $i;
56         }
57         close F;
58 }
59
60 my %s_map;
61 if ($seenmap) {
62         open F, $seenmap;
63         while (my $line = <F>) {
64                 chomp($line);
65                 next if ($line eq '');
66                 $s_map{$line} = 1;
67         }
68         close F;
69 }
70
71 my $doc = XML::LibXML->new->parse_file($file);
72
73 OpenSRF::System->bootstrap_client( config_file => $config );
74 Fieldmapper->import(IDL => OpenSRF::Utils::SettingsClient->new->config_value("IDL"));
75
76 my $cstore = OpenSRF::AppSession->create( 'open-ils.cstore' );
77
78 my $profiles = $cstore->request(
79                 'open-ils.cstore.direct.permission.grp_tree.search.atomic',
80                 { id => { '!=' => undef } },
81 )->gather(1);
82
83 my $orgs = $cstore->request(
84                 'open-ils.cstore.direct.actor.org_unit.search.atomic',
85                 { id => { '!=' => undef } },
86 )->gather(1);
87
88 $profiles = { map { ($_->name => $_->id) } @$profiles };
89 $orgs = { map { ($_->shortname => $_->id) } @$orgs };
90
91 my $starttime = time;
92 my $count = 1;
93 for my $patron ( $doc->documentElement->childNodes ) {
94         next if ($patron->nodeType == 3);
95         my $p = new Fieldmapper::actor::user;
96         my $card = new Fieldmapper::actor::card;
97         my $profile_sce = new Fieldmapper::actor::stat_cat_entry_user_map;
98
99         my $old_profile = $patron->findvalue( 'user_profile' );
100
101         my $bc = $patron->findvalue( 'user_id' );
102         if (exists($s_map{$bc})) {
103                 $count++;
104                 warn "\n!!! already saw barcode $bc, skipping\n";
105                 next;
106         } else {
107                 $s_map{$bc} = 1;
108         }
109
110         unless (defined($bc)) {
111                 my $xml = $patron->toString;
112                 warn "\n!!! no barcode found in UMS data, user number $count, xml => $xml \n";
113                 $count++;
114                 next;
115         }
116
117         my $uid;
118         if (keys %u_map) {
119                 $uid = $u_map{$bc};
120                 unless ($uid) {
121                         $count++;
122                         warn "\n!!! no uid mapping found for barcode $bc\n";
123                         next;
124                 }
125         } else {
126                 next;
127         }
128
129         unless ($uid > 1) {
130                 $count++;
131                 warn "\n!!! user id lower than 2\n";
132                 next;
133         }
134         
135         $card->barcode( $bc );
136         $card->usr( $uid );
137         $card->active( 't' );
138
139         $p->id( $uid );
140         $p->usrname( $bc );
141         $p->passwd( $patron->findvalue( 'user_pin' ) );
142
143         my $new_profile = $p_map{$old_profile} || $default_profile;
144
145         $p->profile( $$profiles{$new_profile} );
146         if (!$p->profile) {
147                 $count++;
148                 warn "\n!!! no new profile found for $old_profile\n";
149                 next;
150         }
151
152         # some defaults
153         $p->standing(1);
154         $p->active('t');
155         $p->deleted('f');
156         $p->master_account('f');
157         $p->super_user('f');
158         $p->usrgroup($uid);
159         $p->claims_returned_count(0);
160         $p->credit_forward_balance(0);
161         $p->last_xact_id('IMPORT-'.$starttime);
162
163         $p->barred('f');
164         $p->barred('t') if ( $patron->findvalue( 'user_status' ) eq 'BARRED' );
165
166         $p->ident_type( $identtypeid );
167         my $id_val = $patron->findvalue( 'user_altid' );
168         $p->ident_value( $id_val ) if ($id_val);
169
170         my ($fname,$mname,$lname) = ($patron->findvalue('first_name'),$patron->findvalue('middle_name'),$patron->findvalue('last_name'));
171
172         $fname =~ s/^\s*//o;
173         $mname =~ s/^\s*//o;
174         $lname =~ s/^\s*//o;
175
176         $fname =~ s/\s*$//o;
177         $mname =~ s/\s*$//o;
178         $lname =~ s/\s*$//o;
179
180         $p->first_given_name( $fname );
181         $p->second_given_name( $mname );
182         $p->family_name( $lname );
183
184         $p->day_phone( $patron->findvalue( 'Address/dayphone' ) );
185         $p->evening_phone( $patron->findvalue( 'Address/homephone' ) );
186         $p->other_phone( $patron->findvalue( 'Address/workphone' ) );
187
188         my $hlib = $$orgs{$patron->findvalue( 'user_library' )};
189         unless ($hlib) {
190                 $count++;
191                 warn "\n!!! no home library found in patron record\n";
192                 next;
193         }
194         $p->home_ou( $hlib );
195
196         $p->dob( parse_date( $patron->findvalue( 'birthdate' ) ) );
197         $p->create_date( parse_date( $patron->findvalue( 'user_priv_granted' ) ) );
198         $p->expire_date( parse_date( $patron->findvalue( 'user_priv_expires' ) ) );
199
200         $p->alert_message("Legacy Import Message: old profile was FIXME")
201                 if ($old_profile eq 'FIXME');
202
203         my $net_access = 1;
204         $net_access = 2 if ($old_profile =~ /^U.I/o);
205         $net_access = 3 if ($old_profile =~ /^X.I/o);
206
207         $p->net_access_level( $net_access );
208
209         $profile_sce->target_usr( $uid );
210         $profile_sce->stat_cat( $profileid );
211         $profile_sce->stat_cat_entry( $old_profile );
212
213         my @addresses;
214         my $mailing_addr_id = $patron->findvalue( 'user_mailingaddr' );
215
216         my $all_valid = 't';
217         for my $addr ( $patron->findnodes( "Address" ) ) {
218                 if (!$p->email) {
219                         $p->email( $patron->findvalue( 'email' ) );
220                 }
221
222                 my $prefix = 'coa_';
223
224                 my $line1 = $addr->findvalue( "${prefix}line1" );
225                 $prefix = 'std_' if (!$line1);
226
227                 $line1 = $addr->findvalue( "${prefix}line1" );
228                 next unless ($line1);
229
230                 my $a = new Fieldmapper::actor::user_address;
231                 $a->usr( $uid );
232                 $a->street1( $line1 );
233                 $a->street2( $addr->findvalue( "${prefix}line2" ) );
234                 $a->city( $addr->findvalue( "${prefix}city" ) );
235                 $a->state( $addr->findvalue( "${prefix}state" ) );
236                 $a->post_code(
237                         $addr->findvalue( "${prefix}zip" ) .
238                         '-' . $addr->findvalue( "${prefix}zip4" )
239                 );
240                 
241                 $a->valid( 'f' );
242                 $a->valid( 't' ) if ($prefix eq 'std_');
243                 $a->valid( 'f' ) if ($prefix eq 'std_' and $addr->findvalue( "${prefix}dpvscore" ) < 3);
244                 
245                 $a->within_city_limits( 'f' );
246                 $a->country('USA');
247
248                 if ($addr->getAttribute('addr_type') == $mailing_addr_id) {
249                         $a->address_type( 'LEGACY MAILING' );
250                 } else {
251                         $a->address_type( 'LEGACY' );
252                 }
253
254                 push @addresses, $a;
255
256                 if ($prefix eq 'coa_') {
257                         $all_valid = 'f';
258                         $prefix = 'std_';
259
260                         $line1 = $addr->findvalue( "${prefix}line1" );
261                         next unless ($line1);
262
263                         $a = new Fieldmapper::actor::user_address;
264                         $a->usr( $uid );
265                         $a->street1( $line1 );
266                         $a->street2( $addr->findvalue( "${prefix}line2" ) );
267                         $a->city( $addr->findvalue( "${prefix}city" ) );
268                         $a->state( $addr->findvalue( "${prefix}state" ) );
269                         $a->post_code(
270                                 $addr->findvalue( "${prefix}zip" ) .
271                                 '-' . $addr->findvalue( "${prefix}zip4" )
272                         );
273                 
274                         $a->valid( 'f' );
275                 
276                         $a->within_city_limits( 'f' );
277                         $a->country('USA');
278
279                         $a->address_type( 'LEGACY' );
280
281                         push @addresses, $a;
282                 }
283         }
284
285         if ($all_valid eq 'f') {
286                 $_->valid('f') for (@addresses);
287         }
288
289         my @notes;
290         for my $note_field ( qw#note comment voter bus_school Address/phone1 Address/phone2# ) {
291                 for my $note ( $patron->findnodes( $note_field) ) {
292                         my $a = new Fieldmapper::actor::usr_note;
293
294                         $a->creator(1);
295                         $a->create_date('now');
296                         $a->usr( $uid );
297                         $a->title( "Legacy ".$note->localName );
298                         $a->value( $note->textContent );
299                         $a->pub( 'f' );
300                         push @notes, $a;
301                 }
302         }
303
304         print STDERR "\r$count     ".$count/(time - $starttime) unless ($count % 100);
305         print JSON->perl2JSON( $_ )."\n" for ($p,$card,$profile_sce,@addresses,@notes);
306
307         $count++;
308 }
309
310 unless ($nosaveseen) {
311         warn "writing seen_map $seenmap...\n";
312
313         open F, ">$seenmap";
314         print F "$_\n" for (keys %s_map);
315         close F;
316 }
317
318 print STDERR "\n";
319
320
321 sub parse_date {
322         my $string = shift;
323         my $group = shift;
324
325         my ($y,$m,$d);
326
327         if ($string eq 'NEVER') {
328                 my (undef,undef,undef,$d,$m,$y) = localtime();
329                 return sprintf('%04d-%02d-%02d', $y + 1920, $m + 1, $d);
330         } elsif (length($string) == 8 && $string =~ /^(\d{4})(\d{2})(\d{2})$/o) {
331                 ($y,$m,$d) = ($1,$2,$3);
332         } elsif ($string =~ /(\d+)\D(\d+)\D(\d+)/o) { #looks like it's parsable
333                 if ( length($3) > 2 )  { # looks like mm.dd.yyyy
334                         if ( $1 < 99 && $2 < 99 && $1 > 0 && $2 > 0 && $3 > 0) {
335                                 if ($1 > 12 && $1 < 31 && $2 < 13) { # well, actually it looks like dd.mm.yyyy
336                                         ($y,$m,$d) = ($3,$2,$1);
337                                 } elsif ($2 > 12 && $2 < 31 && $1 < 13) {
338                                         ($y,$m,$d) = ($3,$1,$2);
339                                 }
340                         }
341                 } elsif ( length($1) > 3 ) { # format probably yyyy.mm.dd
342                         if ( $3 < 99 && $2 < 99 && $1 > 0 && $2 > 0 && $3 > 0) {
343                                 if ($2 > 12 && $2 < 32 && $3 < 13) { # well, actually it looks like yyyy.dd.mm -- why, I don't konw
344                                         ($y,$m,$d) = ($1,$3,$2);
345                                 } elsif ($3 > 12 && $3 < 31 && $2 < 13) {
346                                         ($y,$m,$d) = ($1,$2,$3);
347                                 }
348                         }
349                 } elsif ( $1 < 99 && $2 < 99 && $3 < 99 && $1 > 0 && $2 > 0 && $3 > 0) {
350                         if ($3 < 7) { # probably 2000 or greater, mm.dd.yy
351                                 $y = $3 + 2000;
352                                 if ($1 > 12 && $1 < 32 && $2 < 13) { # well, actually it looks like dd.mm.yyyy
353                                         ($m,$d) = ($2,$1);
354                                 } elsif ($2 > 12 && $2 < 32 && $1 < 13) {
355                                         ($m,$d) = ($1,$2);
356                                 }
357                         } else { # probably before 2000, mm.dd.yy
358                                 $y = $3 + 1900;
359                                 if ($1 > 12 && $1 < 32 && $2 < 13) { # well, actually it looks like dd.mm.yyyy
360                                         ($m,$d) = ($2,$1);
361                                 } elsif ($2 > 12 && $2 < 32 && $1 < 13) {
362                                         ($m,$d) = ($1,$2);
363                                 }
364                         }
365                 }
366         }
367
368         my $date;
369         if ($y && $m && $d) {
370                 try {
371                         $date = sprintf('%04d-%02d-%-2d',$y, $m, $d)
372                                 if (new DateTime ( year => $y, month => $m, day => $d ));
373                 } otherwise {};
374         }
375
376         return $date;
377 }
378