]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Cat.pm
searching for non-deleted CNs when looking for dups
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Cat.pm
1 use strict; use warnings;
2 package OpenILS::Application::Cat;
3 use OpenILS::Application::AppUtils;
4 use OpenSRF::Application;
5 use OpenILS::Application::Cat::Utils;
6 use OpenILS::Application::Cat::Merge;
7 use base qw/OpenSRF::Application/;
8 use Time::HiRes qw(time);
9 use OpenSRF::EX qw(:try);
10 use JSON;
11 use OpenILS::Utils::Fieldmapper;
12 use OpenILS::Event;
13
14 use XML::LibXML;
15 use Unicode::Normalize;
16 use Data::Dumper;
17 use OpenILS::Utils::FlatXML;
18 use OpenILS::Utils::Editor;
19 use OpenILS::Perm;
20 use OpenSRF::Utils::SettingsClient;
21 use OpenSRF::Utils::Logger qw($logger);
22
23 my $apputils = "OpenILS::Application::AppUtils";
24
25 my $utils = "OpenILS::Application::Cat::Utils";
26 my $U = "OpenILS::Application::AppUtils";
27
28 my $conf;
29
30 my %marctemplates;
31
32 sub entityize { 
33         my $stuff = shift;
34         my $form = shift || "";
35
36         if ($form eq 'D') {
37                 $stuff = NFD($stuff);
38         } else {
39                 $stuff = NFC($stuff);
40         }
41
42         $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
43         return $stuff;
44 }
45
46 __PACKAGE__->register_method(
47         method  => "retrieve_marc_template",
48         api_name        => "open-ils.cat.biblio.marc_template.retrieve",
49         notes           => <<"  NOTES");
50         Returns a MARC 'record tree' based on a set of pre-defined templates.
51         Templates include : book
52         NOTES
53
54 sub retrieve_marc_template {
55         my( $self, $client, $type ) = @_;
56
57         return $marctemplates{$type} if defined($marctemplates{$type});
58         $marctemplates{$type} = _load_marc_template($type);
59         return $marctemplates{$type};
60 }
61
62 sub _load_marc_template {
63         my $type = shift;
64
65         if(!$conf) { $conf = OpenSRF::Utils::SettingsClient->new; }
66
67         my $template = $conf->config_value(                                     
68                 "apps", "open-ils.cat","app_settings", "marctemplates", $type );
69         warn "Opening template file $template\n";
70
71         open( F, $template ) or 
72                 throw OpenSRF::EX::ERROR ("Unable to open MARC template file: $template : $@");
73
74         my @xml = <F>;
75         close(F);
76         my $xml = join('', @xml);
77
78         return XML::LibXML->new->parse_string($xml)->documentElement->toString;
79 }
80
81
82
83 __PACKAGE__->register_method(
84         method  => "create_record_xml",
85         api_name        => "open-ils.cat.biblio.record.xml.create.override",
86         signature       => q/@see open-ils.cat.biblio.record.xml.create/);
87
88 __PACKAGE__->register_method(
89         method          => "create_record_xml",
90         api_name                => "open-ils.cat.biblio.record.xml.create",
91         signature       => q/
92                 Inserts a new biblio with the given XML
93         /
94 );
95
96 sub create_record_xml {
97         my( $self, $client, $login, $xml, $source ) = @_;
98         $source ||= 2;
99
100         my $override = 1 if $self->api_name =~ /override/;
101
102         my( $user_obj, $evt ) = $U->checksesperm($login, 'CREATE_MARC');
103         return $evt if $evt;
104
105         $logger->activity("user ".$user_obj->id." creating new MARC record");
106
107         my $meth = $self->method_lookup("open-ils.cat.biblio.record.xml.import");
108
109         $meth = $self->method_lookup(
110                 "open-ils.cat.biblio.record.xml.import.override") if $override;
111
112         my ($s) = $meth->run($login, $xml, 2);
113         return $s;
114 }
115
116
117
118
119 __PACKAGE__->register_method(
120         method  => "biblio_record_xml_import",
121         api_name        => "open-ils.cat.biblio.record.xml.import.override",
122         signature       => q/@see open-ils.cat.biblio.record.xml.import/);
123
124 __PACKAGE__->register_method(
125         method  => "biblio_record_xml_import",
126         api_name        => "open-ils.cat.biblio.record.xml.import",
127         notes           => <<"  NOTES");
128         Takes a marcxml record and imports the record into the database.  In this
129         case, the marcxml record is assumed to be a complete record (i.e. valid
130         MARC).  The title control number is taken from (whichever comes first)
131         tags 001, 039[ab], 020a, 022a, 010, 035a and whichever does not already exist
132         in the database.
133         user_session must have IMPORT_MARC permissions
134         NOTES
135
136
137 sub biblio_record_xml_import {
138         my( $self, $client, $authtoken, $xml, $source) = @_;
139
140         my ($tcn, $tcn_source);
141
142         my $override = 1 if $self->api_name =~ /override/;
143
144         my( $requestor, $evt ) = $U->checksesperm($authtoken, 'IMPORT_MARC');
145         return $evt if $evt;
146
147         my $session = $apputils->start_db_session();
148
149         # parse the XML
150         my $marcxml = XML::LibXML->new->parse_string( $xml );
151         $marcxml->documentElement->setNamespace( 
152                 "http://www.loc.gov/MARC21/slim", "marc", 1 );
153
154         my $xpath = '//marc:controlfield[@tag="001"]';
155         $tcn = $marcxml->documentElement->findvalue($xpath);
156         $logger->info("biblio import located 001 (tcn) value of $tcn");
157
158         $xpath = '//marc:controlfield[@tag="003"]';
159         $tcn_source = $marcxml->documentElement->findvalue($xpath) || "System Local";
160
161         if(my $rec = _tcn_exists($session, $tcn, $tcn_source)) {
162
163                 my $origtcn = $tcn;
164                 $tcn = find_free_tcn( $marcxml, $session );
165
166                 # if we're overriding, try to find a different TCN to use
167                 if( $override ) {
168
169                         $logger->activity("tcn value $tcn already exists, attempting to override");
170
171                         if(!$tcn) {
172                                 return OpenILS::Event->new(
173                                         'OPEN_TCN_NOT_FOUND', payload => $marcxml->toString());
174                         }
175
176                 } else {
177
178                         $logger->warn("tcn value $origtcn already exists in import/create");
179
180                         # otherwise, return event
181                         return OpenILS::Event->new( 
182                                 'TCN_EXISTS', payload => { 
183                                         dup_record      => $rec, 
184                                         tcn                     => $origtcn,
185                                         new_tcn         => $tcn
186                                         } );
187                 }
188
189         } else {
190
191                 $logger->activity("user ".$requestor->id.
192                 " creating new biblio entry with tcn=$tcn and tcn_source $tcn_source");
193         }
194
195
196         my $record = Fieldmapper::biblio::record_entry->new;
197
198         $record->source($source) if ($source);
199         $record->tcn_source($tcn_source);
200         $record->tcn_value($tcn);
201         $record->creator($requestor->id);
202         $record->editor($requestor->id);
203         $record->marc( entityize( $marcxml->documentElement->toString ) );
204
205         my $id = $session->request(
206                 "open-ils.storage.direct.biblio.record_entry.create", $record )->gather(1);
207
208         return $U->DB_UPDATE_FAILED($record) unless $id;
209         $record->id( $id );
210
211         $logger->info("marc create/import created new record $id");
212
213         $apputils->commit_db_session($session);
214
215         $logger->debug("Sending record off to be wormized");
216
217         my $stat = $U->storagereq( 'open-ils.worm.wormize.biblio', $id );
218         throw OpenSRF::EX::ERROR 
219                 ("Unable to wormize imported record") unless $stat;
220
221         return $record;
222 }
223
224 sub find_free_tcn {
225
226         my $marcxml = shift;
227         my $session = shift;
228
229         my $add_039 = 0;
230
231         my $xpath = '//marc:datafield[@tag="039"]/subfield[@code="a"]';
232         my ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
233         $xpath = '//marc:datafield[@tag="039"]/subfield[@code="b"]';
234         my $tcn_source = $marcxml->documentElement->findvalue($xpath) || "System Local";
235
236         if(_tcn_exists($session, $tcn, $tcn_source)) {
237                 $tcn = undef;
238         } else {
239                 $add_039++;
240         }
241
242
243         if(!$tcn) {
244                 $xpath = '//marc:datafield[@tag="020"]/subfield[@code="a"]';
245                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
246                 $tcn_source = "ISBN";
247                 if(_tcn_exists($session, $tcn, $tcn_source)) {$tcn = undef;}
248         }
249
250         if(!$tcn) { 
251                 $xpath = '//marc:datafield[@tag="022"]/subfield[@code="a"]';
252                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
253                 $tcn_source = "ISSN";
254                 if(_tcn_exists($session, $tcn, $tcn_source)) {$tcn = undef;}
255         }
256
257         if(!$tcn) {
258                 $xpath = '//marc:datafield[@tag="010"]';
259                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
260                 $tcn_source = "LCCN";
261                 if(_tcn_exists($session, $tcn, $tcn_source)) {$tcn = undef;}
262         }
263
264         if(!$tcn) {
265                 $xpath = '//marc:datafield[@tag="035"]/subfield[@code="a"]';
266                 ($tcn) = $marcxml->documentElement->findvalue($xpath) =~ /(\w+)\s*$/o;
267                 $tcn_source = "System Legacy";
268                 if(_tcn_exists($session, $tcn, $tcn_source)) {$tcn = undef;}
269
270                 if($tcn) {
271                         $marcxml->documentElement->removeChild(
272                                 $marcxml->documentElement->findnodes( '//datafield[@tag="035"]' )
273                         );
274                 }
275         }
276
277         if ($add_039) {
278                 my $df = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'datafield');
279                 $df->setAttribute( tag => '039' );
280                 $df->setAttribute( ind1 => ' ' );
281                 $df->setAttribute( ind2 => ' ' );
282                 $marcxml->documentElement->appendChild( $df );
283
284                 my $sfa = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'subfield');
285                 $sfa->setAttribute( code => 'a' );
286                 $sfa->appendChild( $marcxml->createTextNode( $tcn ) );
287                 $df->appendChild( $sfa );
288
289                 my $sfb = $marcxml->createElementNS( 'http://www.loc.gov/MARC21/slim', 'subfield');
290                 $sfb->setAttribute( code => 'b' );
291                 $sfb->appendChild( $marcxml->createTextNode( $tcn_source ) );
292                 $df->appendChild( $sfb );
293         }
294
295         return $tcn;
296 }
297
298
299
300 sub _tcn_exists {
301         my $session = shift;
302         my $tcn = shift;
303         my $source = shift;
304
305         if(!$tcn) {return 0;}
306
307         $logger->debug("tcn_exists search for tcn $tcn and source $source");
308
309         my $req = $session->request(      
310                 "open-ils.storage.id_list.biblio.record_entry.search_where.atomic",
311                 { tcn_value => $tcn, tcn_source => $source, deleted => 'f' } );
312
313         my $recs = $req->gather(1);
314
315         if($recs and $recs->[0]) {
316                 $logger->debug("_tcn_exists is true for tcn : $tcn ($source)");
317                 return $recs->[0];
318         }
319
320         $logger->debug("_tcn_exists is false for tcn : $tcn ($source)");
321         return 0;
322 }
323
324
325
326 __PACKAGE__->register_method(
327         method  => "biblio_record_tree_retrieve",
328         api_name        => "open-ils.cat.biblio.record.tree.retrieve",
329 );
330
331 sub biblio_record_tree_retrieve {
332
333         my( $self, $client, $recordid ) = @_;
334
335         my $name = "open-ils.storage.direct.biblio.record_entry.retrieve";
336         my $session = OpenSRF::AppSession->create( "open-ils.storage" );
337         my $request = $session->request( $name, $recordid );
338         my $marcxml = $request->gather(1);
339
340         if(!$marcxml) {
341                 throw OpenSRF::EX::ERROR 
342                         ("No record in database with id $recordid");
343         }
344
345         $session->disconnect();
346         $session->kill_me();
347
348         warn "turning into nodeset\n";
349         my $nodes = OpenILS::Utils::FlatXML->new()->xml_to_nodeset( $marcxml->marc ); 
350         warn "turning nodeset into tree\n";
351         my $tree = $utils->nodeset2tree( $nodes->nodeset );
352
353         $tree->owner_doc( $marcxml->id() );
354
355         warn "returning tree\n";
356
357         return $tree;
358 }
359
360 __PACKAGE__->register_method(
361         method  => "biblio_record_xml_update",
362         api_name        => "open-ils.cat.biblio.record.xml.update",
363         argc            => 3, #(session_id, biblio_tree ) 
364         notes           => <<'  NOTES');
365         Updates the XML of a biblio record entry
366         @param authtoken The session token for the staff updating the record
367         @param docID The record entry ID to update
368         @param xml The new MARCXML record
369         NOTES
370
371 sub biblio_record_xml_update {
372
373         my( $self, $client, $user_session,  $id, $xml ) = @_;
374
375         my $user_obj = $apputils->check_user_session($user_session); 
376
377         if($apputils->check_user_perms(
378                         $user_obj->id, $user_obj->home_ou, "UPDATE_MARC")) {
379                 return OpenILS::Perm->new("UPDATE_MARC"); 
380         }
381
382         $logger->activity("user ".$user_obj->id." updating biblio record $id");
383
384
385         my $session = OpenILS::Application::AppUtils->start_db_session();
386
387         warn "Retrieving biblio record from storage for update\n";
388
389         my $req1 = $session->request(
390                         "open-ils.storage.direct.biblio.record_entry.batch.retrieve", $id );
391         my $biblio = $req1->gather(1);
392
393         warn "retrieved doc $id\n";
394
395         my $doc = XML::LibXML->new->parse_string($xml);
396         throw OpenSRF::EX::ERROR ("Invalid XML in record update: $xml") unless $doc;
397
398         $biblio->marc( entityize( $doc->documentElement->toString ) );
399         $biblio->editor( $user_obj->id );
400         $biblio->edit_date( 'now' );
401
402         warn "Sending updated doc $id to db with xml ".$biblio->marc. "\n";
403
404         my $req = $session->request( 
405                 "open-ils.storage.direct.biblio.record_entry.update", $biblio );
406
407         $req->wait_complete;
408         my $status = $req->recv();
409         if( !$status || $status->isa("Error") || ! $status->content) {
410                 OpenILS::Application::AppUtils->rollback_db_session($session);
411                 if($status->isa("Error")) { throw $status ($status); }
412                 throw OpenSRF::EX::ERROR ("Error updating biblio record");
413         }
414         $req->finish();
415
416         # Send the doc to the wormer for wormizing
417         warn "Starting worm session\n";
418
419         my $success = 0;
420         my $wresp;
421
422         my $wreq = $session->request( "open-ils.worm.wormize.biblio", $id );
423
424         my $w = 0;
425         try {
426                 $w = $wreq->gather(1);
427
428         } catch Error with {
429                 my $e = shift;
430                 warn "wormizing failed, rolling back\n";
431                 OpenILS::Application::AppUtils->rollback_db_session($session);
432
433                 if($e) { throw $e ($e); }
434                 throw OpenSRF::EX::ERROR ("Wormizing Failed for $id" );
435         };
436
437         warn "Committing db session...\n";
438         OpenILS::Application::AppUtils->commit_db_session( $session );
439
440 #       $client->respond_complete($tree);
441
442         warn "Done wormizing\n";
443
444         #use Data::Dumper;
445         #warn "Returning tree:\n";
446         #warn Dumper $tree;
447
448         return $biblio;
449
450 }
451
452
453
454 __PACKAGE__->register_method(
455         method  => "biblio_record_record_metadata",
456         api_name        => "open-ils.cat.biblio.record.metadata.retrieve",
457         argc            => 1, #(session_id, biblio_tree ) 
458         notes           => "Walks the tree and commits any changed nodes " .
459                                         "adds any new nodes, and deletes any deleted nodes",
460 );
461
462 sub biblio_record_record_metadata {
463         my( $self, $client, $authtoken, $ids ) = @_;
464
465         return [] unless $ids and @$ids;
466
467         my $editor = OpenILS::Utils::Editor->new(authtoken => $authtoken);
468         return $editor->event unless $editor->checkauth;
469         return $editor->event unless $editor->allowed('VIEW_USER');
470
471         my @results;
472
473         for(@$ids) {
474                 return $editor->event unless 
475                         my $rec = $editor->retrieve_biblio_record_entry($_);
476                 $rec->creator($editor->retrieve_actor_user($rec->creator));
477                 $rec->editor($editor->retrieve_actor_user($rec->editor));
478                 $rec->clear_marc; # slim the record down
479                 push( @results, $rec );
480         }
481
482         return \@results;
483 }
484
485
486
487 __PACKAGE__->register_method(
488         method  => "biblio_record_marc_cn",
489         api_name        => "open-ils.cat.biblio.record.marc_cn.retrieve",
490         argc            => 1, #(bib id ) 
491 );
492
493 sub biblio_record_marc_cn {
494         my( $self, $client, $id ) = @_;
495
496         my $session = OpenSRF::AppSession->create("open-ils.storage");
497         my $marc = $session
498                 ->request("open-ils.storage.direct.biblio.record_entry.retrieve", $id )
499                 ->gather(1)
500                 ->marc;
501
502         my $doc = XML::LibXML->new->parse_string($marc);
503         $doc->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
504         
505         my @res;
506         for my $tag ( qw/050 055 060 070 080 082 086 088 090 092 096 098 099/ ) {
507                 my @node = $doc->findnodes("//marc:datafield[\@tag='$tag']");
508                 for my $x (@node) {
509                         my $cn = $x->findvalue("marc:subfield[\@code='a' or \@code='b']");
510                         push @res, {$tag => $cn} if ($cn);
511                 }
512         }
513
514         return \@res
515 }
516
517 # gets the username
518 #sub _get_userid_by_id {
519 #
520 #       my @ids = @_;
521 #       my @users;
522 #
523 #       my $session = OpenSRF::AppSession->create( "open-ils.storage" );
524 #       my $request = $session->request( 
525 #               "open-ils.storage.direct.actor.user.batch.retrieve.atomic", @ids );
526 #
527 #       $request->wait_complete;
528 #       my $response = $request->recv();
529 #       if(!$request->complete) { return undef; }
530 #
531 #       if($response->isa("Error")){
532 #               throw $response ($response);
533 #       }
534 #
535 #       for my $u (@{$response->content}) {
536 #               next unless ref($u);
537 #               push @users, $u->usrname;
538 #       }
539 #
540 #       $request->finish;
541 #       $session->disconnect;
542 #       $session->kill_me();
543 #
544 #       return @users;
545 #}
546
547 sub _get_id_by_userid {
548
549         my @users = @_;
550         my @ids;
551
552         my $session = OpenSRF::AppSession->create( "open-ils.storage" );
553         my $request = $session->request( 
554                 "open-ils.storage.direct.actor.user.search.usrname.atomic", @users );
555
556         $request->wait_complete;
557         my $response = $request->recv();
558         if(!$request->complete) { 
559                 throw OpenSRF::EX::ERROR ("no response from storage on user retrieve");
560         }
561
562         if(UNIVERSAL::isa( $response, "Error")){
563                 throw $response ($response);
564         }
565
566         for my $u (@{$response->content}) {
567                 next unless ref($u);
568                 push @ids, $u->id();
569         }
570
571         $request->finish;
572         $session->disconnect;
573         $session->kill_me();
574
575         return @ids;
576 }
577
578
579 # commits metadata objects to the db
580 sub _update_record_metadata {
581
582         my ($session, @docs ) = @_;
583
584         for my $doc (@docs) {
585
586                 my $user_obj = $doc->{user};
587                 my $docid = $doc->{docid};
588
589                 warn "Updating metata for doc $docid\n";
590
591                 my $request = $session->request( 
592                         "open-ils.storage.direct.biblio.record_entry.retrieve", $docid );
593                 my $record = $request->gather(1);
594
595                 warn "retrieved record\n";
596                 my ($id) = _get_id_by_userid($user_obj->usrname);
597
598                 warn "got $id from _get_id_by_userid\n";
599                 $record->editor($id);
600                 
601                 warn "Grabbed the record, updating and moving on\n";
602
603                 $request = $session->request( 
604                         "open-ils.storage.direct.biblio.record_entry.update", $record );
605                 $request->gather(1);
606         }
607
608         warn "committing metarecord update\n";
609
610         return 1;
611 }
612
613
614
615 __PACKAGE__->register_method(
616         method  => "orgs_for_title",
617         api_name        => "open-ils.cat.actor.org_unit.retrieve_by_title"
618 );
619
620 sub orgs_for_title {
621         my( $self, $client, $record_id ) = @_;
622
623         my $vols = $apputils->simple_scalar_request(
624                 "open-ils.storage",
625                 "open-ils.storage.direct.asset.call_number.search_where.atomic",
626                 { record => $record_id, deleted => 'f' });
627                 #"open-ils.storage.direct.asset.call_number.search.record.atomic",
628
629         my $orgs = { map {$_->owning_lib => 1 } @$vols };
630         return [ keys %$orgs ];
631 }
632
633
634 __PACKAGE__->register_method(
635         method  => "retrieve_copies",
636         api_name        => "open-ils.cat.asset.copy_tree.retrieve");
637
638 __PACKAGE__->register_method(
639         method  => "retrieve_copies",
640         api_name        => "open-ils.cat.asset.copy_tree.global.retrieve");
641
642 # user_session may be null/undef
643 sub retrieve_copies {
644
645         my( $self, $client, $user_session, $docid, @org_ids ) = @_;
646
647         if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
648
649         $docid = "$docid";
650
651         warn " $$ retrieving copy tree for orgs @org_ids and doc $docid at " . time() . "\n";
652
653         # grabbing copy trees should be available for everyone..
654         if(!@org_ids and $user_session) {
655                 my $user_obj = 
656                         OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
657                         @org_ids = ($user_obj->home_ou);
658         }
659
660         if( $self->api_name =~ /global/ ) {
661                 warn "performing global copy_tree search for $docid\n";
662                 return _build_volume_list( { record => $docid } );
663
664         } else {
665
666                 my @all_vols;
667                 for my $orgid (@org_ids) {
668                         my $vols = _build_volume_list( 
669                                         { record => $docid, owning_lib => $orgid } );
670                         warn "Volumes built for org $orgid\n";
671                         push( @all_vols, @$vols );
672                 }
673                 
674                 warn " $$ Finished copy_tree at " . time() . "\n";
675                 return \@all_vols;
676         }
677
678         return undef;
679 }
680
681
682 sub _build_volume_list {
683         my $search_hash = shift;
684
685         $search_hash->{deleted} = 'f';
686
687         my      $session = OpenSRF::AppSession->create( "open-ils.storage" );
688         
689
690         my $request = $session->request( 
691                         "open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
692                         #"open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
693
694         my $vols = $request->gather(1);
695         my @volumes;
696
697         for my $volume (@$vols) {
698
699                 warn "Grabbing copies for volume: " . $volume->id . "\n";
700                 my $creq = $session->request(
701                         "open-ils.storage.direct.asset.copy.search_where.atomic", 
702                         { call_number => $volume->id , deleted => 'f' });
703                         #"open-ils.storage.direct.asset.copy.search.call_number.atomic", $volume->id );
704
705                 my $copies = $creq->gather(1);
706
707                 $copies = [ sort { $a->barcode cmp $b->barcode } @$copies  ];
708
709                 $volume->copies($copies);
710
711                 push( @volumes, $volume );
712         }
713
714
715         $session->disconnect();
716         return \@volumes;
717
718 }
719
720
721 =head old code
722
723 # -----------------------------------------------------------------
724 # Fleshed volume tree batch add/update.  This does everything a 
725 # volume tree could want, add, update, delete
726 # -----------------------------------------------------------------
727 __PACKAGE__->register_method(
728         method  => "volume_tree_fleshed_update",
729         api_name        => "open-ils.cat.asset.volume_tree.fleshed.batch.update",
730 );
731 sub volume_tree_fleshed_update {
732
733         my( $self, $client, $user_session, $volumes ) = @_;
734         return undef unless $volumes;
735
736         my $user_obj = $apputils->check_user_session($user_session);
737
738         # first lets see if we have any dup volume labels
739 #       my %volumes;
740 #       my @volumes = @$volumes;
741 #       for my $vol (@volumes) {
742 #               $volumes{$_->owning_lib} = {}  unless $volumes{$_->owning_lib};
743 #               $volumes{$_->record} = {}  unless $volumes{$_->record};
744 #               if($volumes{$_->record}->{$_->label}) {
745 #                       $logger->info("Duplicate volume label found ".
746 #                               "for record ".$_->record." and label ".$_->label);
747 #                       $volumes{$_->record}->{$_->label}++;
748 #               } else {
749 #                       $volumes{$_->record}->{$_->label} = 1;
750 #               }
751 #       }
752 #
753 #       for my $r (values %volumes) {
754 #               for my $l (keys %{$volumes{$r}}) {
755 #                       return OpenILS::Event->new('CALL_NUMBER_EXISTS', payload => $l)
756 #                               if $volumes{$r}{$l} > 1;
757 #               }
758 #       }
759
760
761         my $session = $apputils->start_db_session();
762         warn "Looping on volumes in fleshed volume tree update\n";
763
764         # cycle through the volumes provided and update/create/delete where necessary
765         for my $volume (@$volumes) {
766
767                 warn "updating volume " . $volume->id . "\n";
768
769                 my $update_copy_list = $volume->copies;
770
771
772                 if( $volume->isdeleted) {
773                         my $status = _delete_volume($session, $volume, $user_obj);
774                         #if(!$status) {
775                                 #throw OpenSRF::EX::ERROR
776                                         #("Volume delete failed for volume " . $volume->id);
777                         #}
778                         if(UNIVERSAL::isa($status, "Fieldmapper::perm_ex")) { return $status; }
779
780                 } elsif( $volume->isnew ) {
781
782                         $volume->clear_id;
783                         $volume->editor($user_obj->id);
784                         $volume->creator($user_obj->id);
785                         $volume = _add_volume($session, $volume, $user_obj);
786                         use Data::Dumper;
787                         warn Dumper $volume;
788                         if($volume and UNIVERSAL::isa($volume, "Fieldmapper::perm_ex")) { return $volume; }
789
790                 } elsif( $volume->ischanged ) {
791
792                         $volume->editor($user_obj->id);
793                         my $stat = _update_volume($session, $volume, $user_obj);
794                         if($stat and UNIVERSAL::isa($stat, "Fieldmapper::perm_ex")) { return $stat; }
795                 }
796
797
798                 if( ! $volume->isdeleted ) {
799                         for my $copy (@{$update_copy_list}) {
800         
801                                 $copy->editor($user_obj->id);
802                                 warn "updating copy for volume " . $volume->id . "\n";
803         
804                                 if( $copy->isnew ) {
805         
806                                         $copy->clear_id;
807                                         $copy->call_number($volume->id);
808                                         $copy->creator($user_obj->id);
809                                         $copy = _fleshed_copy_update($session,$copy,$user_obj);
810         
811                                 } elsif( $copy->ischanged ) {
812                                         $copy->call_number($volume->id);
813                                         $copy = _fleshed_copy_update($session, $copy, $user_obj);
814         
815                                 } elsif( $copy->isdeleted ) {
816                                         warn "Deleting copy " . $copy->id . " for volume " . $volume->id . "\n";
817                                         my $status = _fleshed_copy_update($session, $copy, $user_obj);
818                                         warn "Copy delete returned a status of $status\n";
819                                 }
820                         }
821                 }
822         }
823
824         $apputils->commit_db_session($session);
825         return scalar(@$volumes);
826 }
827
828
829 sub _delete_volume {
830         my( $session, $volume, $user_obj ) = @_;
831
832         if($apputils->check_user_perms(
833                         $user_obj->id, $user_obj->home_ou, "DELETE_VOLUME")) {
834                 return OpenILS::Perm->new("DELETE_VOLUME"); }
835
836         #$volume = _find_volume($session, $volume);
837         warn "Deleting volume " . $volume->id . "\n";
838
839         my $copies = $session->request(
840                 "open-ils.storage.direct.asset.copy.search_where.atomic", 
841                 { call_number => $volume->id, deleted => 'f' } )->gather(1);
842                 #"open-ils.storage.direct.asset.copy.search.call_number.atomic",
843
844         if(@$copies) {
845                 throw OpenSRF::EX::ERROR 
846                         ("Cannot remove volume with copies attached");
847         }
848
849         my $req = $session->request(
850                 "open-ils.storage.direct.asset.call_number.delete",
851                 $volume );
852         return $req->gather(1);
853 }
854
855
856 sub _update_volume {
857         my($session, $volume, $user_obj) = @_;
858         if($apputils->check_user_perms(
859                         $user_obj->id, $user_obj->home_ou, "UPDATE_VOLUME")) {
860                 return OpenILS::Perm->new("UPDATE_VOLUME"); }
861
862         my $req = $session->request(
863                 "open-ils.storage.direct.asset.call_number.update",
864                 $volume );
865         my $status = $req->gather(1);
866 }
867
868 sub _add_volume {
869
870         my($session, $volume, $user_obj) = @_;
871
872         if($apputils->check_user_perms(
873                         $user_obj->id, $user_obj->home_ou, "CREATE_VOLUME")) {
874                 warn "User does not have priveleges to create new volumes\n";
875                 return OpenILS::Perm->new("CREATE_VOLUME"); 
876         }
877
878         my $request = $session->request( 
879                 "open-ils.storage.direct.asset.call_number.create", $volume );
880
881         my $id = $request->gather(1);
882
883         if( $id == 0 ) {
884                 OpenILS::Application::AppUtils->rollback_db_session($session);
885                 throw OpenSRF::EX::ERROR (" * -> Error creating new volume");
886         }
887
888         $volume->id($id);
889         warn "received new volume id: $id\n";
890         return $volume;
891
892 }
893
894 =cut
895
896
897
898
899 __PACKAGE__->register_method(
900         method  => "fleshed_copy_update",
901         api_name        => "open-ils.cat.asset.copy.fleshed.batch.update",);
902
903 __PACKAGE__->register_method(
904         method  => "fleshed_copy_update",
905         api_name        => "open-ils.cat.asset.copy.fleshed.batch.update.override",);
906
907 sub fleshed_copy_update {
908         my( $self, $conn, $auth, $copies ) = @_;
909         my( $reqr, $evt ) = $U->checkses($auth);
910         return $evt if $evt;
911         my $editor = OpenILS::Utils::Editor->new( requestor => $reqr, xact => 1 );
912         my $override = $self->api_name =~ /override/;
913         $evt = update_fleshed_copies( $editor, $override, undef, $copies);
914         return $evt if $evt;
915         $editor->finish;
916         $logger->info("fleshed copy update successfully updated ".scalar(@$copies)." copies");
917         return 1;
918 }
919
920
921
922 =head old code
923 sub _delete_copy {
924         my($session, $copy, $user_obj) = @_;
925
926         if($apputils->check_user_perms(
927                         $user_obj->id, $user_obj->home_ou, "DELETE_COPY")) {
928                 return OpenILS::Perm->new("DELETE_COPY"); }
929
930         warn "Deleting copy " . $copy->id . "\n";
931         my $request = $session->request(
932                 "open-ils.storage.direct.asset.copy.delete",
933                 $copy );
934         return $request->gather(1);
935 }
936
937 sub _create_copy {
938         my($session, $copy, $user_obj) = @_;
939
940         if($apputils->check_user_perms(
941                         $user_obj->id, $user_obj->home_ou, "CREATE_COPY")) {
942                 return OpenILS::Perm->new("CREATE_COPY"); }
943
944         my $request = $session->request(
945                 "open-ils.storage.direct.asset.copy.create",
946                 $copy );
947         my $id = $request->gather(1);
948
949         if($id < 1) {
950                 throw OpenSRF::EX::ERROR
951                         ("Unable to create new copy " . Dumper($copy));
952         }
953         $copy->id($id);
954         warn "Created copy " . $copy->id . "\n";
955
956         return $copy;
957
958 }
959
960 sub _update_copy {
961         my($session, $copy, $user_obj) = @_;
962
963         my $evt = $apputils->check_perms($user_obj->id, $copy->circ_lib, 'UPDATE_COPY');
964         return $evt if $evt; #XXX NOT YET HANDLED BY CALLER
965
966         my $status = $apputils->simplereq(      
967                 'open-ils.storage',
968                 "open-ils.storage.direct.asset.copy.update", $copy );
969         $logger->debug("Successfully updated copy " . $copy->id );
970         return $status;
971 }
972
973
974
975 # -----------------------------------------------------------------
976 # Creates/Updates/Deletes a fleshed asset.copy.  
977 # adds/deletes copy stat_cat maps where necessary
978 # -----------------------------------------------------------------
979 sub _fleshed_copy_update {
980         my($session, $copy, $editor) = @_;
981
982         my $stat_cat_entries = $copy->stat_cat_entries;
983         $copy->editor($editor->id);
984         
985         # in case we're fleshed
986         if(ref($copy->status))          {$copy->status( $copy->status->id ); }
987         if(ref($copy->location))        {$copy->location( $copy->location->id ); }
988         if(ref($copy->circ_lib))        {$copy->circ_lib( $copy->circ_lib->id ); }
989
990         warn "Updating copy " . Dumper($copy) . "\n";
991
992         if( $copy->isdeleted ) { 
993                 return _delete_copy($session, $copy, $editor);
994         } elsif( $copy->isnew ) {
995                 $copy = _create_copy($session, $copy, $editor);
996         } elsif( $copy->ischanged ) {
997                 _update_copy($session, $copy, $editor);
998         }
999
1000         
1001         return 1 unless ( $stat_cat_entries and @$stat_cat_entries );
1002
1003         my $stat_maps = $session->request(
1004                 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.search.owning_copy.atomic",
1005                 $copy->id )->gather(1);
1006
1007         if(!$copy->isnew) { _delete_stale_maps($session, $stat_maps, $copy); }
1008         
1009         # go through the stat cat update/create process
1010         for my $stat_entry (@{$stat_cat_entries}){ 
1011                 _copy_update_stat_cats( $session, $copy, $stat_maps, $stat_entry, $editor );
1012         }
1013         
1014         return 1;
1015 }
1016
1017
1018 # -----------------------------------------------------------------
1019 # Deletes stat maps attached to this copy in the database that
1020 # are no longer attached to the current copy
1021 # -----------------------------------------------------------------
1022 sub _delete_stale_maps {
1023         my( $session, $stat_maps, $copy) = @_;
1024
1025         warn "Deleting stale stat maps for copy " . $copy->id . "\n";
1026         for my $map (@$stat_maps) {
1027         # if there is no stat cat entry on the copy who's id matches the
1028         # current map's id, remove the map from the database
1029         if(! grep { $_->id == $map->stat_cat_entry } @{$copy->stat_cat_entries} ) {
1030                 my $req = $session->request(
1031                         "open-ils.storage.direct.asset.stat_cat_entry_copy_map.delete", $map );
1032                 $req->gather(1);
1033                 }
1034         }
1035
1036         return $stat_maps;
1037 }
1038
1039
1040 # -----------------------------------------------------------------
1041 # Searches the stat maps to see if '$entry' already exists on
1042 # the given copy.  If it does not, a new stat map is created
1043 # for the given entry and copy
1044 # -----------------------------------------------------------------
1045 sub _copy_update_stat_cats {
1046         my ( $session, $copy, $stat_maps, $entry, $editor ) = @_;
1047
1048         warn "Updating stat maps for copy " . $copy->id . "\n";
1049
1050         # see if this map already exists
1051         for my $map (@$stat_maps) {
1052                 if( $map->stat_cat_entry == $entry->id ) {return;}
1053         }
1054
1055         warn "Creating new stat map for stat  " . 
1056                 $entry->stat_cat . " and copy " . $copy->id . "\n";
1057
1058         # if not, create it
1059         my $new_map = Fieldmapper::asset::stat_cat_entry_copy_map->new();
1060
1061         $new_map->stat_cat( $entry->stat_cat );
1062         $new_map->stat_cat_entry( $entry->id );
1063         $new_map->owning_copy( $copy->id );
1064
1065         warn "New map is " . Dumper($new_map) . "\n";
1066
1067         my $request = $session->request(
1068                 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.create",
1069                 $new_map );
1070         my $status = $request->gather(1);
1071         warn "created new map with id $status\n";
1072
1073 }
1074
1075 =cut
1076
1077
1078 __PACKAGE__->register_method(
1079         method => 'merge',
1080         api_name        => 'open-ils.cat.biblio.records.merge',
1081         signature       => q/
1082                 Merges a group of records
1083                 @param auth The login session key
1084                 @param master The id of the record all other r
1085                         ecords should be merged into
1086                 @param records Array of records to be merged into the master record
1087                 @return 1 on success, Event on error.
1088         /
1089 );
1090
1091 sub merge {
1092         my( $self, $conn, $auth, $master, $records ) = @_;
1093         my( $reqr, $evt ) = $U->checkses($auth);
1094         return $evt if $evt;
1095         my %r = map { $_ => 1 } (@$records, $master); # unique the ids
1096         $records = [ keys %r ];
1097         my $editor = OpenILS::Utils::Editor->new( requestor => $reqr, xact => 1 );
1098         my $v = OpenILS::Application::Cat::Merge::merge_records($editor, $master, $records);
1099         return $v if $v;
1100         $editor->finish;
1101         return 1;
1102 }
1103
1104
1105
1106
1107 # ---------------------------------------------------------------------------
1108 # ---------------------------------------------------------------------------
1109
1110 # returns true if the given title (id) has no un-deleted
1111 # copies attached
1112 sub title_is_empty {
1113         my( $editor, $rid ) = @_;
1114         my $cnlist = $editor->search_asset_call_number(
1115                 { record => $rid, deleted => 'f' }, { idlist => 1 } );
1116         return 1 unless @$cnlist;
1117
1118         for my $cn (@$cnlist) {
1119                 my $copylist = $editor->search_asset_copy(
1120                         { call_number => $cn, deleted => 'f' }, { idlist => 1 });
1121                 return 0 if @$copylist;
1122         }
1123
1124         return 1;
1125 }
1126
1127
1128 __PACKAGE__->register_method(
1129         method  => "fleshed_volume_update",
1130         api_name        => "open-ils.cat.asset.volume.fleshed.batch.update",);
1131
1132 __PACKAGE__->register_method(
1133         method  => "fleshed_volume_update",
1134         api_name        => "open-ils.cat.asset.volume.fleshed.batch.update.override",);
1135
1136 sub fleshed_volume_update {
1137         my( $self, $conn, $auth, $volumes ) = @_;
1138         my( $reqr, $evt ) = $U->checkses($auth);
1139         return $evt if $evt;
1140
1141         my $override = ($self->api_name =~ /override/);
1142         my $editor = OpenILS::Utils::Editor->new( requestor => $reqr, xact => 1 );
1143
1144         for my $vol (@$volumes) {
1145                 $logger->info("vol-update: investigating volume ".$vol->id);
1146
1147                 $vol->editor($reqr->id);
1148                 $vol->edit_date('now');
1149
1150                 my $copies = $vol->copies;
1151                 $vol->clear_copies;
1152
1153                 if( $vol->isdeleted ) {
1154
1155                         $logger->info("vol-update: deleting volume");
1156                         my $cs = $editor->search_asset_copy(
1157                                 { call_number => $vol->id, deleted => 'f' } );
1158                         return OpenILS::Event->new(
1159                                 'VOLUME_NOT_EMPTY', payload => $vol->id ) if @$cs;
1160
1161                         $vol->deleted('t');
1162                         return $editor->event unless
1163                                 $editor->update_asset_call_number($vol);
1164
1165                         
1166                 } elsif( $vol->isnew ) {
1167                         $logger->info("vol-update: creating volume");
1168                         $evt = create_volume( $override, $editor, $vol );
1169                         return $evt if $evt;
1170
1171                 } elsif( $vol->ischanged ) {
1172                         $logger->info("vol-update: update volume");
1173                         return $editor->event unless
1174                                 $editor->update_asset_call_number($vol);
1175                         return $evt if $evt;
1176                 }
1177
1178                 # now update any attached copies
1179                 if( @$copies and !$vol->isdeleted ) {
1180                         $_->call_number($vol->id) for @$copies;
1181                         $evt = update_fleshed_copies( $editor, $override, $vol, $copies );
1182                         return $evt if $evt;
1183                 }
1184         }
1185
1186         $editor->finish;
1187         return scalar(@$volumes);
1188 }
1189
1190
1191 # this does the actual work
1192 sub update_fleshed_copies {
1193         my( $editor, $override, $vol, $copies ) = @_;
1194
1195         my $evt;
1196         my $fetchvol = ($vol) ? 0 : 1;
1197
1198         my %cache;
1199         $cache{$vol->id} = $vol if $vol;
1200
1201         for my $copy (@$copies) {
1202
1203                 my $copyid = $copy->id;
1204                 $logger->info("vol-update: inspecting copy $copyid");
1205
1206                 if( !($vol = $cache{$copy->call_number}) ) {
1207                         $vol = $cache{$copy->call_number} = 
1208                                 $editor->retrieve_asset_call_number($copy->call_number);
1209                         return $editor->event unless $vol;
1210                 }
1211
1212                 $copy->editor($editor->requestor->id);
1213                 $copy->edit_date('now');
1214
1215                 $copy->status( $copy->status->id ) if ref($copy->status);
1216                 $copy->location( $copy->location->id ) if ref($copy->location);
1217                 $copy->circ_lib( $copy->circ_lib->id ) if ref($copy->circ_lib);
1218                 
1219                 my $sc_entries = $copy->stat_cat_entries;
1220                 $copy->clear_stat_cat_entries;
1221
1222                 if( $copy->isdeleted ) {
1223                         $evt = delete_copy($editor, $override, $vol, $copy);
1224                         return $evt if $evt;
1225
1226                 } elsif( $copy->isnew ) {
1227                         $evt = create_copy( $editor, $vol, $copy );
1228                         return $evt if $evt;
1229
1230                 } elsif( $copy->ischanged ) {
1231
1232                         $logger->info("vol-update: updating copy $copyid");
1233                         return $editor->event unless
1234                                 $editor->update_asset_copy(
1235                                         $copy, {checkperm=>1, permorg=>$vol->owning_lib});
1236                         return $evt if $evt;
1237                 }
1238
1239                 $copy->stat_cat_entries( $sc_entries );
1240                 $evt = update_copy_stat_entries($editor, $copy);
1241                 return $evt if $evt;
1242         }
1243
1244         $logger->debug("vol-update: done updating copy batch");
1245
1246         return undef;
1247 }
1248
1249 sub delete_copy {
1250         my( $editor, $override, $vol, $copy ) = @_;
1251
1252         $logger->info("vol-update: deleting copy ".$copy->id);
1253         $copy->deleted('t');
1254
1255         $editor->update_asset_copy(
1256                 $copy, {checkperm=>1, permorg=>$vol->owning_lib})
1257                 or return $editor->event;
1258
1259         if( title_is_empty($editor, $vol->record) ) {
1260
1261                 if( $override ) {
1262
1263                         # delete this volume if it's not already marked as deleted
1264                         if(!$vol->deleted || $vol->deleted =~ /f/io || ! $vol->isdeleted) {
1265                                 $vol->deleted('t');
1266                                 $editor->update_asset_call_number($vol, {checkperm=>0})
1267                                         or return $editor->event;
1268                         }
1269
1270                         # then delete the record this volume points to
1271                         my $rec = $editor->retrieve_biblio_record_entry($vol->record)
1272                                 or return $editor->event;
1273
1274                         if( !$rec->deleted ) {
1275                                 $rec->deleted('t');
1276                                 $rec->active('f');
1277                                 $editor->update_biblio_record_entry($rec, {checkperm=>0})
1278                                         or return $editor->event;
1279                         }
1280
1281                 } else {
1282                         return OpenILS::Event->new('TITLE_LAST_COPY');
1283                 }
1284         }
1285
1286         return undef;
1287 }
1288
1289 sub create_copy {
1290         my( $editor, $vol, $copy ) = @_;
1291
1292         my $existing = $editor->search_asset_copy(
1293                 { barcode => $copy->barcode } );
1294         
1295         return OpenILS::Event->new('ITEM_BARCODE_EXISTS') if @$existing;
1296
1297         $copy->clear_id;
1298         $copy->creator($editor->requestor->id);
1299         $copy->create_date('now');
1300
1301         $editor->create_asset_copy(
1302                 $copy, {checkperm=>1, permorg=>$vol->owning_lib})
1303                 or return $editor->event;
1304
1305         return undef;
1306 }
1307
1308 sub update_copy_stat_entries {
1309         my( $editor, $copy ) = @_;
1310
1311         my $evt;
1312         my $entries = $copy->stat_cat_entries;
1313         return undef unless ($entries and @$entries);
1314
1315         my $maps = $editor->search_asset_stat_cat_entry_copy_map({owning_copy=>$copy->id});
1316
1317         if(!$copy->isnew) {
1318                 # if there is no stat cat entry on the copy who's id matches the
1319                 # current map's id, remove the map from the database
1320                 for my $map (@$maps) {
1321                         if(! grep { $_->id == $map->stat_cat_entry } @$entries ) {
1322
1323                                 $logger->info("copy update found stale ".
1324                                         "stat cat entry map ".$map->id. " on copy ".$copy->id);
1325
1326                                 $editor->delete_asset_stat_cat_entry_copy_map($map)
1327                                         or return $editor->event;
1328                         }
1329                 }
1330         }
1331         
1332         # go through the stat cat update/create process
1333         for my $entry (@$entries) { 
1334
1335                 # if this link already exists in the DB, don't attempt to re-create it
1336                 next if( grep{$_->stat_cat_entry == $entry->id} @$maps );
1337         
1338                 my $new_map = Fieldmapper::asset::stat_cat_entry_copy_map->new();
1339                 
1340                 $new_map->stat_cat( $entry->stat_cat );
1341                 $new_map->stat_cat_entry( $entry->id );
1342                 $new_map->owning_copy( $copy->id );
1343
1344                 $editor->create_asset_stat_cat_entry_copy_map($new_map)
1345                         or return $editor->event;
1346
1347                 $logger->info("copy update created new stat cat entry map ".$editor->data);
1348         }
1349
1350         return undef;
1351 }
1352
1353
1354 sub create_volume {
1355         my( $override, $editor, $vol ) = @_;
1356         my $evt;
1357
1358
1359         # first lets see if there are any collisions
1360         my $vols = $editor->search_asset_call_number( { 
1361                         owning_lib      => $vol->owning_lib,
1362                         record          => $vol->record,
1363                         label                   => $vol->label,
1364                         deleted         => 'f'
1365                 }
1366         );
1367
1368         my $label = undef;
1369         if(@$vols) {
1370                 if($override) { 
1371                         $label = $vol->label;
1372                 } else {
1373                         return OpenILS::Event->new(
1374                                 'VOLUME_LABEL_EXISTS', payload => $vol->id);
1375                 }
1376         }
1377
1378         # create a temp label so we can create the volume, then de-dup it
1379         $vol->label( '__SYSTEM_TMP_'.time) if $label;
1380
1381         $vol->creator($editor->requestor->id);
1382         $vol->create_date('now');
1383         $vol->clear_id;
1384
1385         $editor->create_asset_call_number($vol) or return $editor->event;
1386
1387         if($label) {
1388                 # now restore the label and merge into the existing record
1389                 $vol->label($label);
1390                 (undef, $evt) = 
1391                         OpenILS::Application::Cat::Merge::merge_volumes($editor, [$vol], $$vols[0]);
1392                 return $evt if $evt;
1393         }
1394
1395         return undef;
1396 }
1397
1398
1399
1400
1401
1402
1403 1;