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 base qw/OpenSRF::Application/;
7 use Time::HiRes qw(time);
8 use OpenSRF::EX qw(:try);
10 use OpenILS::Utils::Fieldmapper;
13 use OpenILS::Utils::FlatXML;
16 my $apputils = "OpenILS::Application::AppUtils";
18 my $utils = "OpenILS::Application::Cat::Utils";
21 __PACKAGE__->register_method(
22 method => "biblio_record_tree_import",
23 api_name => "open-ils.cat.biblio.record.tree.import",
25 Takes a record tree and imports the record into the database. In this
26 case, the record tree is assumed to be a complete record (i.e. valid
27 MARC. The title control number is taken from (whichever comes first)
28 tags 001, 020, 022, 010, 035 and whichever does not already exist
30 user_session must have IMPORT_MARC permissions
34 sub biblio_record_tree_import {
35 my( $self, $client, $user_session, $tree) = @_;
36 my $user_obj = $apputils->check_user_session($user_session);
38 if($apputils->check_user_perms(
39 $user_obj->id, $user_obj->home_ou, "IMPORT_MARC")) {
40 return OpenILS::Perm->new("IMPORT_MARC");
43 warn "importing new record " . Dumper($tree) . "\n";
45 my $nodeset = $utils->tree2nodeset($tree);
46 warn "turned into nodeset " . Dumper($nodeset) . "\n";
48 # copy the doc so that we can mangle the namespace.
49 my $marcxml = OpenILS::Utils::FlatXML->new()->nodeset_to_xml($nodeset);
50 my $copy_marcxml = XML::LibXML->new->parse_string($marcxml->toString);
52 $marcxml->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
56 warn "Starting db session in import\n";
57 my $session = $apputils->start_db_session();
58 my $source = 2; # system local source
60 my $xpath = '//controlfield[@tag="001"]';
61 $tcn = $marcxml->documentElement->findvalue($xpath);
62 if(_tcn_exists($session, $tcn)) {$tcn = undef;}
63 my $tcn_source = "External";
67 $xpath = '//datafield[@tag="020"]';
68 $tcn = $marcxml->documentElement->findvalue($xpath);
70 if(_tcn_exists($session, $tcn)) {$tcn = undef;}
74 $xpath = '//datafield[@tag="022"]';
75 $tcn = $marcxml->documentElement->findvalue($xpath);
77 if(_tcn_exists($session, $tcn)) {$tcn = undef;}
81 $xpath = '//datafield[@tag="010"]';
82 $tcn = $marcxml->documentElement->findvalue($xpath);
84 if(_tcn_exists($session, $tcn)) {$tcn = undef;}
88 $xpath = '//datafield[@tag="035"]';
89 $tcn = $marcxml->documentElement->findvalue($xpath);
90 $tcn_source = "System";
91 if(_tcn_exists($session, $tcn)) {$tcn = undef;}
97 warn "Record import with tcn: $tcn and source $tcn_source\n";
99 my $record = Fieldmapper::biblio::record_entry->new;
101 $record->source($source);
102 $record->tcn_source($tcn_source);
103 $record->tcn_value($tcn);
104 $record->creator($user_obj->id);
105 $record->editor($user_obj->id);
106 $record->marc($copy_marcxml->toString);
109 my $req = $session->request(
110 "open-ils.storage.direct.biblio.record_entry.create", $record );
112 my $id = $req->gather(1);
114 if(!$id) { throw OpenSRF::EX::ERROR ("Unable to create new record_entry from import"); }
115 warn "received id: $id from record_entry create\n";
117 $apputils->commit_db_session($session);
119 $session = OpenSRF::AppSession->create("open-ils.storage");
121 my $wreq = $session->request("open-ils.worm.wormize", $id)->gather(1);
122 warn "Done worming record $id\n";
124 if(!$wreq) { throw OpenSRF::EX::ERROR ("Unable to wormize imported record"); }
126 return $self->biblio_record_tree_retrieve($client, $id);
134 if(!$tcn) {return 0;}
136 my $req = $session->request(
137 "open-ils.storage.direct.biblio.record_entry.search.tcn_value.atomic",
139 my $recs = $req->gather(1);
141 if($recs and $recs->[0]) {
149 __PACKAGE__->register_method(
150 method => "biblio_record_tree_retrieve",
151 api_name => "open-ils.cat.biblio.record.tree.retrieve",
154 sub biblio_record_tree_retrieve {
156 my( $self, $client, $recordid ) = @_;
158 my $name = "open-ils.storage.direct.biblio.record_entry.retrieve";
159 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
160 my $request = $session->request( $name, $recordid );
161 my $marcxml = $request->gather(1);
164 throw OpenSRF::EX::ERROR
165 ("No record in database with id $recordid");
168 $session->disconnect();
171 warn "turning into nodeset\n";
172 my $nodes = OpenILS::Utils::FlatXML->new()->xml_to_nodeset( $marcxml->marc );
173 warn "turning nodeset into tree\n";
174 my $tree = $utils->nodeset2tree( $nodes->nodeset );
176 $tree->owner_doc( $marcxml->id() );
178 warn "returning tree\n";
183 __PACKAGE__->register_method(
184 method => "biblio_record_tree_commit",
185 api_name => "open-ils.cat.biblio.record.tree.commit",
186 argc => 3, #(session_id, biblio_tree )
187 notes => <<" NOTES");
188 Walks the tree and commits any changed nodes
189 adds any new nodes, and deletes any deleted nodes
190 The record to commit must already exist or this
194 sub biblio_record_tree_commit {
196 my( $self, $client, $user_session, $tree ) = @_;
198 throw OpenSRF::EX::InvalidArg
199 ("Not enough args to to open-ils.cat.biblio.record.tree.commit")
200 unless ( $user_session and $tree );
202 my $user_obj = $apputils->check_user_session($user_session);
204 if($apputils->check_user_perms(
205 $user_obj->id, $user_obj->home_ou, "UPDATE_MARC")) {
206 return OpenILS::Perm->new("UPDATE_MARC");
211 my $docid = $tree->owner_doc();
212 my $session = OpenILS::Application::AppUtils->start_db_session();
214 warn "Retrieving biblio record from storage for update\n";
216 my $req1 = $session->request(
217 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", $docid );
218 my $biblio = $req1->gather(1);
220 warn "retrieved doc $docid\n";
223 # turn the tree into a nodeset
224 my $nodeset = $utils->tree2nodeset($tree);
225 $nodeset = $utils->clean_nodeset($nodeset);
227 if(!defined($docid)) { # be sure
228 for my $node (@$nodeset) {
229 $docid = $node->owner_doc();
230 last if defined($docid);
234 # turn the nodeset into a doc
235 my $marcxml = OpenILS::Utils::FlatXML->new()->nodeset_to_xml( $nodeset );
237 $biblio->marc( $marcxml->toString() );
239 warn "Starting db session\n";
241 my $x = _update_record_metadata( $session, { user => $user_obj, docid => $docid } );
242 OpenILS::Application::AppUtils->rollback_db_session($session) unless $x;
244 warn "Sending updated doc $docid to db\n";
245 my $req = $session->request( "open-ils.storage.direct.biblio.record_entry.update", $biblio );
248 my $status = $req->recv();
249 if( !$status || $status->isa("Error") || ! $status->content) {
250 OpenILS::Application::AppUtils->rollback_db_session($session);
251 if($status->isa("Error")) { throw $status ($status); }
252 throw OpenSRF::EX::ERROR ("Error updating biblio record");
256 # Send the doc to the wormer for wormizing
257 warn "Starting worm session\n";
262 my $wreq = $session->request( "open-ils.worm.wormize", $docid );
269 warn "wormizing failed, rolling back\n";
270 OpenILS::Application::AppUtils->rollback_db_session($session);
272 if($e) { throw $e ($e); }
273 throw OpenSRF::EX::ERROR ("Wormizing Failed for $docid" );
276 warn "Committing db session...\n";
277 OpenILS::Application::AppUtils->commit_db_session( $session );
279 $nodeset = OpenILS::Utils::FlatXML->new()->xmldoc_to_nodeset($marcxml);
280 $tree = $utils->nodeset2tree($nodeset->nodeset);
281 $tree->owner_doc($docid);
283 # $client->respond_complete($tree);
285 warn "Done wormizing\n";
288 #warn "Returning tree:\n";
297 __PACKAGE__->register_method(
298 method => "biblio_record_record_metadata",
299 api_name => "open-ils.cat.biblio.record.metadata.retrieve",
300 argc => 1, #(session_id, biblio_tree )
301 notes => "Walks the tree and commits any changed nodes " .
302 "adds any new nodes, and deletes any deleted nodes",
305 sub biblio_record_record_metadata {
306 my( $self, $client, @ids ) = @_;
308 if(!@ids){return undef;}
310 my $session = OpenSRF::AppSession->create("open-ils.storage");
311 my $request = $session->request(
312 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
316 while( my $response = $request->recv() ) {
319 throw OpenSRF::EX::ERROR ("No Response from Storage");
321 if($response->isa("Error")) {
322 throw $response ($response->stringify);
325 my $record_entry = $response->content;
327 my $creator = $record_entry->creator;
328 my $editor = $record_entry->editor;
330 ($creator, $editor) = _get_userid_by_id($creator, $editor);
332 $record_entry->creator($creator);
333 $record_entry->editor($editor);
335 push @$results, $record_entry;
340 $session->disconnect();
348 sub _get_userid_by_id {
353 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
354 my $request = $session->request(
355 "open-ils.storage.direct.actor.user.batch.retrieve.atomic", @ids );
357 $request->wait_complete;
358 my $response = $request->recv();
359 if(!$request->complete) { return undef; }
361 if($response->isa("Error")){
362 throw $response ($response);
365 for my $u (@{$response->content}) {
367 push @users, $u->usrname;
371 $session->disconnect;
377 sub _get_id_by_userid {
382 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
383 my $request = $session->request(
384 "open-ils.storage.direct.actor.user.search.usrname.atomic", @users );
386 $request->wait_complete;
387 my $response = $request->recv();
388 if(!$request->complete) {
389 throw OpenSRF::EX::ERROR ("no response from storage on user retrieve");
392 if(UNIVERSAL::isa( $response, "Error")){
393 throw $response ($response);
396 for my $u (@{$response->content}) {
402 $session->disconnect;
409 # commits metadata objects to the db
410 sub _update_record_metadata {
412 my ($session, @docs ) = @_;
414 for my $doc (@docs) {
416 my $user_obj = $doc->{user};
417 my $docid = $doc->{docid};
419 warn "Updating metata for doc $docid\n";
421 my $request = $session->request(
422 "open-ils.storage.direct.biblio.record_entry.retrieve", $docid );
423 my $record = $request->gather(1);
425 warn "retrieved record\n";
426 my ($id) = _get_id_by_userid($user_obj->usrname);
428 warn "got $id from _get_id_by_userid\n";
429 $record->editor($id);
431 warn "Grabbed the record, updating and moving on\n";
433 $request = $session->request(
434 "open-ils.storage.direct.biblio.record_entry.update", $record );
438 warn "committing metarecord update\n";
445 __PACKAGE__->register_method(
446 method => "orgs_for_title",
447 api_name => "open-ils.cat.actor.org_unit.retrieve_by_title"
451 my( $self, $client, $record_id ) = @_;
453 my $vols = $apputils->simple_scalar_request(
455 "open-ils.storage.direct.asset.call_number.search.record.atomic",
458 my $orgs = { map {$_->owning_lib => 1 } @$vols };
459 return [ keys %$orgs ];
464 __PACKAGE__->register_method(
465 method => "retrieve_copies",
466 api_name => "open-ils.cat.asset.copy_tree.retrieve");
468 __PACKAGE__->register_method(
469 method => "retrieve_copies",
470 api_name => "open-ils.cat.asset.copy_tree.global.retrieve");
472 # user_session may be null/undef
473 sub retrieve_copies {
475 my( $self, $client, $user_session, $docid, @org_ids ) = @_;
477 if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
481 warn " $$ retrieving copy tree for orgs @org_ids and doc $docid at " . time() . "\n";
483 # grabbing copy trees should be available for everyone..
484 if(!@org_ids and $user_session) {
486 OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
487 @org_ids = ($user_obj->home_ou);
490 if( $self->api_name =~ /global/ ) {
491 warn "performing global copy_tree search for $docid\n";
492 return _build_volume_list( { record => $docid } );
497 for my $orgid (@org_ids) {
498 my $vols = _build_volume_list(
499 { record => $docid, owning_lib => $orgid } );
500 warn "Volumes built for org $orgid\n";
501 push( @all_vols, @$vols );
504 warn " $$ Finished copy_tree at " . time() . "\n";
512 sub _build_volume_list {
513 my $search_hash = shift;
515 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
518 my $request = $session->request(
519 "open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
521 my $vols = $request->gather(1);
524 for my $volume (@$vols) {
526 warn "Grabbing copies for volume: " . $volume->id . "\n";
527 my $creq = $session->request(
528 "open-ils.storage.direct.asset.copy.search.call_number.atomic",
530 my $copies = $creq->gather(1);
532 $volume->copies($copies);
534 push( @volumes, $volume );
538 $session->disconnect();
544 # -----------------------------------------------------------------
545 # Fleshed volume tree batch add/update. This does everything a
546 # volume tree could want, add, update, delete
547 # -----------------------------------------------------------------
548 __PACKAGE__->register_method(
549 method => "volume_tree_fleshed_update",
550 api_name => "open-ils.cat.asset.volume_tree.fleshed.batch.update",
552 sub volume_tree_fleshed_update {
554 my( $self, $client, $user_session, $volumes ) = @_;
555 return undef unless $volumes;
557 my $user_obj = $apputils->check_user_session($user_session);
560 my $session = $apputils->start_db_session();
561 warn "Looping on volumes in fleshed volume tree update\n";
563 # cycle through the volumes provided and update/create/delete where necessary
564 for my $volume (@$volumes) {
566 warn "updating volume " . $volume->id . "\n";
568 my $update_copy_list = $volume->copies;
571 if( $volume->isdeleted) {
572 my $status = _delete_volume($session, $volume, $user_obj);
574 throw OpenSRF::EX::ERROR
575 ("Volume delete failed for volume " . $volume->id);
577 if(UNIVERSAL::isa($status, "Fieldmapper::perm_ex")) { return $status; }
579 } elsif( $volume->isnew ) {
582 $volume->editor($user_obj->id);
583 $volume->creator($user_obj->id);
584 $volume = _add_volume($session, $volume, $user_obj);
587 if($volume and UNIVERSAL::isa($volume, "Fieldmapper::perm_ex")) { return $volume; }
589 } elsif( $volume->ischanged ) {
591 $volume->editor($user_obj->id);
592 my $stat = _update_volume($session, $volume, $user_obj);
593 if($stat and UNIVERSAL::isa($stat, "Fieldmapper::perm_ex")) { return $stat; }
597 if( ! $volume->isdeleted ) {
598 for my $copy (@{$update_copy_list}) {
600 $copy->editor($user_obj->id);
601 warn "updating copy for volume " . $volume->id . "\n";
606 $copy->call_number($volume->id);
607 $copy->creator($user_obj->id);
608 $copy = _fleshed_copy_update($session,$copy,$user_obj);
610 } elsif( $copy->ischanged ) {
611 $copy->call_number($volume->id);
612 $copy = _fleshed_copy_update($session, $copy, $user_obj);
614 } elsif( $copy->isdeleted ) {
615 warn "Deleting copy " . $copy->id . " for volume " . $volume->id . "\n";
616 my $status = _fleshed_copy_update($session, $copy, $user_obj);
617 warn "Copy delete returned a status of $status\n";
623 $apputils->commit_db_session($session);
624 return scalar(@$volumes);
629 my( $session, $volume, $user_obj ) = @_;
631 if($apputils->check_user_perms(
632 $user_obj->id, $user_obj->home_ou, "DELETE_VOLUME")) {
633 return OpenILS::Perm->new("DELETE_VOLUME"); }
635 #$volume = _find_volume($session, $volume);
636 warn "Deleting volume " . $volume->id . "\n";
638 my $copies = $session->request(
639 "open-ils.storage.direct.asset.copy.search.call_number.atomic",
640 $volume->id )->gather(1);
642 throw OpenSRF::EX::ERROR
643 ("Cannot remove volume with copies attached");
646 my $req = $session->request(
647 "open-ils.storage.direct.asset.call_number.delete",
649 return $req->gather(1);
654 my($session, $volume, $user_obj) = @_;
655 if($apputils->check_user_perms(
656 $user_obj->id, $user_obj->home_ou, "UPDATE_VOLUME")) {
657 return OpenILS::Perm->new("UPDATE_VOLUME"); }
659 my $req = $session->request(
660 "open-ils.storage.direct.asset.call_number.update",
662 my $status = $req->gather(1);
667 my($session, $volume, $user_obj) = @_;
669 if($apputils->check_user_perms(
670 $user_obj->id, $user_obj->home_ou, "CREATE_VOLUME")) {
671 warn "User does not have priveleges to create new volumes\n";
672 return OpenILS::Perm->new("CREATE_VOLUME");
675 my $request = $session->request(
676 "open-ils.storage.direct.asset.call_number.create", $volume );
678 my $id = $request->gather(1);
681 OpenILS::Application::AppUtils->rollback_db_session($session);
682 throw OpenSRF::EX::ERROR (" * -> Error creating new volume");
686 warn "received new volume id: $id\n";
694 __PACKAGE__->register_method(
695 method => "fleshed_copy_update",
696 api_name => "open-ils.cat.asset.copy.fleshed.batch.update",
699 sub fleshed_copy_update {
700 my($self, $client, $user_session, $copies) = @_;
702 my $user_obj = $apputils->check_user_session($user_session);
703 my $session = $apputils->start_db_session();
705 for my $copy (@$copies) {
706 _fleshed_copy_update($session, $copy, $user_obj);
709 $apputils->commit_db_session($session);
716 my($session, $copy, $user_obj) = @_;
718 if($apputils->check_user_perms(
719 $user_obj->id, $user_obj->home_ou, "DELETE_COPY")) {
720 return OpenILS::Perm->new("DELETE_COPY"); }
722 warn "Deleting copy " . $copy->id . "\n";
723 my $request = $session->request(
724 "open-ils.storage.direct.asset.copy.delete",
726 return $request->gather(1);
730 my($session, $copy, $user_obj) = @_;
732 if($apputils->check_user_perms(
733 $user_obj->id, $user_obj->home_ou, "CREATE_COPY")) {
734 return OpenILS::Perm->new("CREATE_COPY"); }
736 my $request = $session->request(
737 "open-ils.storage.direct.asset.copy.create",
739 my $id = $request->gather(1);
742 throw OpenSRF::EX::ERROR
743 ("Unable to create new copy " . Dumper($copy));
746 warn "Created copy " . $copy->id . "\n";
753 my($session, $copy, $user_obj) = @_;
755 if($apputils->check_user_perms(
756 $user_obj->id, $user_obj->home_ou, "UPDATE_COPY")) {
757 return OpenILS::Perm->new("UPDATE_COPY"); }
759 my $request = $session->request(
760 "open-ils.storage.direct.asset.copy.update", $copy );
761 my $status = $request->gather(1);
762 warn "Updated copy " . $copy->id . "\n";
767 # -----------------------------------------------------------------
768 # Creates/Updates/Deletes a fleshed asset.copy.
769 # adds/deletes copy stat_cat maps where necessary
770 # -----------------------------------------------------------------
771 sub _fleshed_copy_update {
772 my($session, $copy, $editor) = @_;
774 my $stat_cat_entries = $copy->stat_cat_entries;
775 $copy->editor($editor->id);
777 # in case we're fleshed
778 if(ref($copy->status)) {$copy->status( $copy->status->id ); }
779 if(ref($copy->location)) {$copy->location( $copy->location->id ); }
780 if(ref($copy->circ_lib)) {$copy->circ_lib( $copy->circ_lib->id ); }
782 warn "Updating copy " . Dumper($copy) . "\n";
784 if( $copy->isdeleted ) {
785 return _delete_copy($session, $copy, $editor);
786 } elsif( $copy->isnew ) {
787 $copy = _create_copy($session, $copy, $editor);
788 } elsif( $copy->ischanged ) {
789 _update_copy($session, $copy, $editor);
793 if(!@$stat_cat_entries) { return 1; }
795 my $stat_maps = $session->request(
796 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.search.owning_copy.atomic",
797 $copy->id )->gather(1);
799 if(!$copy->isnew) { _delete_stale_maps($session, $stat_maps, $copy); }
801 # go through the stat cat update/create process
802 for my $stat_entry (@{$stat_cat_entries}){
803 _copy_update_stat_cats( $session, $copy, $stat_maps, $stat_entry, $editor );
810 # -----------------------------------------------------------------
811 # Deletes stat maps attached to this copy in the database that
812 # are no longer attached to the current copy
813 # -----------------------------------------------------------------
814 sub _delete_stale_maps {
815 my( $session, $stat_maps, $copy) = @_;
817 warn "Deleting stale stat maps for copy " . $copy->id . "\n";
818 for my $map (@$stat_maps) {
819 # if there is no stat cat entry on the copy who's id matches the
820 # current map's id, remove the map from the database
821 if(! grep { $_->id == $map->stat_cat_entry } @{$copy->stat_cat_entries} ) {
822 my $req = $session->request(
823 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.delete", $map );
832 # -----------------------------------------------------------------
833 # Searches the stat maps to see if '$entry' already exists on
834 # the given copy. If it does not, a new stat map is created
835 # for the given entry and copy
836 # -----------------------------------------------------------------
837 sub _copy_update_stat_cats {
838 my ( $session, $copy, $stat_maps, $entry, $editor ) = @_;
840 warn "Updating stat maps for copy " . $copy->id . "\n";
842 # see if this map already exists
843 for my $map (@$stat_maps) {
844 if( $map->stat_cat_entry == $entry->id ) {return;}
847 warn "Creating new stat map for stat " .
848 $entry->stat_cat . " and copy " . $copy->id . "\n";
851 my $new_map = Fieldmapper::asset::stat_cat_entry_copy_map->new();
853 $new_map->stat_cat( $entry->stat_cat );
854 $new_map->stat_cat_entry( $entry->id );
855 $new_map->owning_copy( $copy->id );
857 warn "New map is " . Dumper($new_map) . "\n";
859 my $request = $session->request(
860 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.create",
862 my $status = $request->gather(1);
863 warn "created new map with id $status\n";