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;
15 my $apputils = "OpenILS::Application::AppUtils";
17 my $utils = "OpenILS::Application::Cat::Utils";
20 __PACKAGE__->register_method(
21 method => "biblio_record_tree_import",
22 api_name => "open-ils.cat.biblio.record.tree.import",
24 Takes a record tree and imports the record into the database. In this
25 case, the record tree is assumed to be a complete record (i.e. valid
26 MARC. The title control number is taken from (whichever comes first)
27 tags 001, 020, 022, 010, 035 and whichever does not already exist
29 user_session must have IMPORT_MARC permissions
33 sub biblio_record_tree_import {
34 my( $self, $client, $user_session, $tree) = @_;
35 my $user_obj = $apputils->check_user_session($user_session);
37 if($apputils->check_user_perms(
38 $user_obj->id, $user_obj->home_ou, "IMPORT_MARC")) {
39 return OpenILS::Perm->new("IMPORT_MARC");
42 warn "importing new record " . Dumper($tree) . "\n";
44 my $nodeset = $utils->tree2nodeset($tree);
45 warn "turned into nodeset " . Dumper($nodeset) . "\n";
47 # copy the doc so that we can mangle the namespace.
48 my $marcxml = OpenILS::Utils::FlatXML->new()->nodeset_to_xml($nodeset);
49 my $copy_marcxml = XML::LibXML->new->parse_string($marcxml->toString);
51 $marcxml->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
55 warn "Starting db session in import\n";
56 my $session = $apputils->start_db_session();
57 my $source = 2; # system local source
59 my $xpath = '//controlfield[@tag="001"]';
60 $tcn = $marcxml->documentElement->findvalue($xpath);
61 if(_tcn_exists($session, $tcn)) {$tcn = undef;}
62 my $tcn_source = "External";
66 $xpath = '//datafield[@tag="020"]';
67 $tcn = $marcxml->documentElement->findvalue($xpath);
69 if(_tcn_exists($session, $tcn)) {$tcn = undef;}
73 $xpath = '//datafield[@tag="022"]';
74 $tcn = $marcxml->documentElement->findvalue($xpath);
76 if(_tcn_exists($session, $tcn)) {$tcn = undef;}
80 $xpath = '//datafield[@tag="010"]';
81 $tcn = $marcxml->documentElement->findvalue($xpath);
83 if(_tcn_exists($session, $tcn)) {$tcn = undef;}
87 $xpath = '//datafield[@tag="035"]';
88 $tcn = $marcxml->documentElement->findvalue($xpath);
89 $tcn_source = "System";
90 if(_tcn_exists($session, $tcn)) {$tcn = undef;}
93 warn "Record import with tcn: $tcn and source $tcn_source\n";
95 my $record = Fieldmapper::biblio::record_entry->new;
97 $record->source($source);
98 $record->tcn_source($tcn_source);
99 $record->tcn_value($tcn);
100 $record->creator($user_obj->id);
101 $record->editor($user_obj->id);
102 $record->marc($copy_marcxml->toString);
105 my $req = $session->request(
106 "open-ils.storage.direct.biblio.record_entry.create",
108 my $id = $req->gather(1);
110 my $wreq = $session->request("open-ils.worm.wormize", $id);
113 $apputils->commit_db_session($session);
115 return $self->biblio_record_tree_retrieve($client, $id);
122 if(!$tcn) {return 0;}
124 my $req = $session->request(
125 "open-ils.storage.direct.biblio.record_entry.search.tcn_value.atomic",
127 my $recs = $req->gather(1);
129 if($recs and $recs->[0]) {
137 __PACKAGE__->register_method(
138 method => "biblio_record_tree_retrieve",
139 api_name => "open-ils.cat.biblio.record.tree.retrieve",
142 sub biblio_record_tree_retrieve {
144 my( $self, $client, $recordid ) = @_;
146 my $name = "open-ils.storage.direct.biblio.record_entry.retrieve";
147 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
148 my $request = $session->request( $name, $recordid );
149 my $marcxml = $request->gather(1);
152 throw OpenSRF::EX::ERROR
153 ("No record in database with id $recordid");
156 $session->disconnect();
159 warn "turning into nodeset\n";
160 my $nodes = OpenILS::Utils::FlatXML->new()->xml_to_nodeset( $marcxml->marc );
161 warn "turning nodeset into tree\n";
162 my $tree = $utils->nodeset2tree( $nodes->nodeset );
164 $tree->owner_doc( $marcxml->id() );
166 warn "returning tree\n";
171 __PACKAGE__->register_method(
172 method => "biblio_record_tree_commit",
173 api_name => "open-ils.cat.biblio.record.tree.commit",
174 argc => 3, #(session_id, biblio_tree )
175 notes => <<" NOTES");
176 Walks the tree and commits any changed nodes
177 adds any new nodes, and deletes any deleted nodes
178 The record to commit must already exist or this
182 sub biblio_record_tree_commit {
184 my( $self, $client, $user_session, $tree ) = @_;
186 throw OpenSRF::EX::InvalidArg
187 ("Not enough args to to open-ils.cat.biblio.record.tree.commit")
188 unless ( $user_session and $tree );
190 my $user_obj = $apputils->check_user_session($user_session);
192 if($apputils->check_user_perms(
193 $user_obj->id, $user_obj->home_ou, "UPDATE_MARC")) {
194 return OpenILS::Perm->new("UPDATE_MARC");
199 my $docid = $tree->owner_doc();
200 my $session = OpenILS::Application::AppUtils->start_db_session();
202 warn "Retrieving biblio record from storage for update\n";
204 my $req1 = $session->request(
205 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", $docid );
206 my $biblio = $req1->gather(1);
208 warn "retrieved doc $docid\n";
211 # turn the tree into a nodeset
212 my $nodeset = $utils->tree2nodeset($tree);
213 $nodeset = $utils->clean_nodeset($nodeset);
215 if(!defined($docid)) { # be sure
216 for my $node (@$nodeset) {
217 $docid = $node->owner_doc();
218 last if defined($docid);
222 # turn the nodeset into a doc
223 my $marcxml = OpenILS::Utils::FlatXML->new()->nodeset_to_xml( $nodeset );
225 $biblio->marc( $marcxml->toString() );
227 warn "Starting db session\n";
229 my $x = _update_record_metadata( $session, { user => $user_obj, docid => $docid } );
230 OpenILS::Application::AppUtils->rollback_db_session($session) unless $x;
232 warn "Sending updated doc $docid to db\n";
233 my $req = $session->request( "open-ils.storage.direct.biblio.record_entry.update", $biblio );
236 my $status = $req->recv();
237 if( !$status || $status->isa("Error") || ! $status->content) {
238 OpenILS::Application::AppUtils->rollback_db_session($session);
239 if($status->isa("Error")) { throw $status ($status); }
240 throw OpenSRF::EX::ERROR ("Error updating biblio record");
244 # Send the doc to the wormer for wormizing
245 warn "Starting worm session\n";
250 my $wreq = $session->request( "open-ils.worm.wormize", $docid );
257 warn "wormizing failed, rolling back\n";
258 OpenILS::Application::AppUtils->rollback_db_session($session);
260 if($e) { throw $e ($e); }
261 throw OpenSRF::EX::ERROR ("Wormizing Failed for $docid" );
264 warn "Committing db session...\n";
265 OpenILS::Application::AppUtils->commit_db_session( $session );
267 $nodeset = OpenILS::Utils::FlatXML->new()->xmldoc_to_nodeset($marcxml);
268 $tree = $utils->nodeset2tree($nodeset->nodeset);
269 $tree->owner_doc($docid);
271 # $client->respond_complete($tree);
273 warn "Done wormizing\n";
276 #warn "Returning tree:\n";
285 __PACKAGE__->register_method(
286 method => "biblio_record_record_metadata",
287 api_name => "open-ils.cat.biblio.record.metadata.retrieve",
288 argc => 1, #(session_id, biblio_tree )
289 notes => "Walks the tree and commits any changed nodes " .
290 "adds any new nodes, and deletes any deleted nodes",
293 sub biblio_record_record_metadata {
294 my( $self, $client, @ids ) = @_;
296 if(!@ids){return undef;}
298 my $session = OpenSRF::AppSession->create("open-ils.storage");
299 my $request = $session->request(
300 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", @ids );
304 while( my $response = $request->recv() ) {
307 throw OpenSRF::EX::ERROR ("No Response from Storage");
309 if($response->isa("Error")) {
310 throw $response ($response->stringify);
313 my $record_entry = $response->content;
315 my $creator = $record_entry->creator;
316 my $editor = $record_entry->editor;
318 ($creator, $editor) = _get_userid_by_id($creator, $editor);
320 $record_entry->creator($creator);
321 $record_entry->editor($editor);
323 push @$results, $record_entry;
328 $session->disconnect();
336 sub _get_userid_by_id {
341 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
342 my $request = $session->request(
343 "open-ils.storage.direct.actor.user.batch.retrieve.atomic", @ids );
345 $request->wait_complete;
346 my $response = $request->recv();
347 if(!$request->complete) { return undef; }
349 if($response->isa("Error")){
350 throw $response ($response);
353 for my $u (@{$response->content}) {
355 push @users, $u->usrname;
359 $session->disconnect;
365 sub _get_id_by_userid {
370 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
371 my $request = $session->request(
372 "open-ils.storage.direct.actor.user.search.usrname.atomic", @users );
374 $request->wait_complete;
375 my $response = $request->recv();
376 if(!$request->complete) {
377 throw OpenSRF::EX::ERROR ("no response from storage on user retrieve");
380 if(UNIVERSAL::isa( $response, "Error")){
381 throw $response ($response);
384 for my $u (@{$response->content}) {
390 $session->disconnect;
397 # commits metadata objects to the db
398 sub _update_record_metadata {
400 my ($session, @docs ) = @_;
402 for my $doc (@docs) {
404 my $user_obj = $doc->{user};
405 my $docid = $doc->{docid};
407 warn "Updating metata for doc $docid\n";
409 my $request = $session->request(
410 "open-ils.storage.direct.biblio.record_entry.retrieve", $docid );
411 my $record = $request->gather(1);
413 warn "retrieved record\n";
414 my ($id) = _get_id_by_userid($user_obj->usrname);
416 warn "got $id from _get_id_by_userid\n";
417 $record->editor($id);
419 warn "Grabbed the record, updating and moving on\n";
421 $request = $session->request(
422 "open-ils.storage.direct.biblio.record_entry.update", $record );
426 warn "committing metarecord update\n";
433 __PACKAGE__->register_method(
434 method => "orgs_for_title",
435 api_name => "open-ils.cat.actor.org_unit.retrieve_by_title"
439 my( $self, $client, $record_id ) = @_;
441 my $vols = $apputils->simple_scalar_request(
443 "open-ils.storage.direct.asset.call_number.search.record.atomic",
446 my $orgs = { map {$_->owning_lib => 1 } @$vols };
447 return [ keys %$orgs ];
452 __PACKAGE__->register_method(
453 method => "retrieve_copies",
454 api_name => "open-ils.cat.asset.copy_tree.retrieve");
456 __PACKAGE__->register_method(
457 method => "retrieve_copies",
458 api_name => "open-ils.cat.asset.copy_tree.global.retrieve");
460 # user_session may be null/undef
461 sub retrieve_copies {
463 my( $self, $client, $user_session, $docid, @org_ids ) = @_;
465 if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
469 warn " $$ retrieving copy tree for orgs @org_ids and doc $docid at " . time() . "\n";
471 # grabbing copy trees should be available for everyone..
472 if(!@org_ids and $user_session) {
474 OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
475 @org_ids = ($user_obj->home_ou);
478 if( $self->api_name =~ /global/ ) {
479 warn "performing global copy_tree search for $docid\n";
480 return _build_volume_list( { record => $docid } );
485 for my $orgid (@org_ids) {
486 my $vols = _build_volume_list(
487 { record => $docid, owning_lib => $orgid } );
488 warn "Volumes built for org $orgid\n";
489 push( @all_vols, @$vols );
492 warn " $$ Finished copy_tree at " . time() . "\n";
500 sub _build_volume_list {
501 my $search_hash = shift;
503 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
506 my $request = $session->request(
507 "open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
509 my $vols = $request->gather(1);
512 for my $volume (@$vols) {
514 warn "Grabbing copies for volume: " . $volume->id . "\n";
515 my $creq = $session->request(
516 "open-ils.storage.direct.asset.copy.search.call_number.atomic",
518 my $copies = $creq->gather(1);
520 $volume->copies($copies);
522 push( @volumes, $volume );
526 $session->disconnect();
532 # -----------------------------------------------------------------
533 # Fleshed volume tree batch add/update. This does everything a
534 # volume tree could want, add, update, delete
535 # -----------------------------------------------------------------
536 __PACKAGE__->register_method(
537 method => "volume_tree_fleshed_update",
538 api_name => "open-ils.cat.asset.volume_tree.fleshed.batch.update",
540 sub volume_tree_fleshed_update {
542 my( $self, $client, $user_session, $volumes ) = @_;
543 return undef unless $volumes;
545 my $user_obj = $apputils->check_user_session($user_session);
548 my $session = $apputils->start_db_session();
549 warn "Looping on volumes in fleshed volume tree update\n";
551 # cycle through the volumes provided and update/create/delete where necessary
552 for my $volume (@$volumes) {
554 warn "updating volume " . $volume->id . "\n";
556 my $update_copy_list = $volume->copies;
559 if( $volume->isdeleted) {
560 my $status = _delete_volume($session, $volume, $user_obj);
562 throw OpenSRF::EX::ERROR
563 ("Volume delete failed for volume " . $volume->id);
565 if(UNIVERSAL::ISA($status, "OpenILS::Perm")) { return $status; }
567 } elsif( $volume->isnew ) {
570 $volume->editor($user_obj->id);
571 $volume->creator($user_obj->id);
572 $volume = _add_volume($session, $volume, $user_obj);
573 if($volume and UNIVERSAL::ISA($volume, "OpenILS::Perm")) { return $volume; }
575 } elsif( $volume->ischanged ) {
577 $volume->editor($user_obj->id);
578 my $stat = _update_volume($session, $volume, $user_obj);
579 if($stat and UNIVERSAL::ISA($stat, "OpenILS::Perm")) { return $stat; }
583 if( ! $volume->isdeleted ) {
584 for my $copy (@{$update_copy_list}) {
586 $copy->editor($user_obj->id);
587 warn "updating copy for volume " . $volume->id . "\n";
592 $copy->call_number($volume->id);
593 $copy->creator($user_obj->id);
594 $copy = _fleshed_copy_update($session,$copy,$user_obj->id);
596 } elsif( $copy->ischanged ) {
597 $copy->call_number($volume->id);
598 $copy = _fleshed_copy_update($session, $copy, $user_obj->id);
600 } elsif( $copy->isdeleted ) {
601 warn "Deleting copy " . $copy->id . " for volume " . $volume->id . "\n";
602 my $status = _fleshed_copy_update($session, $copy, $user_obj->id);
603 warn "Copy delete returned a status of $status\n";
609 $apputils->commit_db_session($session);
610 return scalar(@$volumes);
615 my( $session, $volume, $user_obj ) = @_;
617 if($apputils->check_user_perms(
618 $user_obj->id, $user_obj->home_ou, "DELETE_VOLUME")) {
619 return OpenILS::Perm->new("DELETE_VOLUME"); }
621 #$volume = _find_volume($session, $volume);
622 warn "Deleting volume " . $volume->id . "\n";
624 my $copies = $session->request(
625 "open-ils.storage.direct.asset.copy.search.call_number.atomic",
626 $volume->id )->gather(1);
628 throw OpenSRF::EX::ERROR
629 ("Cannot remove volume with copies attached");
632 my $req = $session->request(
633 "open-ils.storage.direct.asset.call_number.delete",
635 return $req->gather(1);
640 my($session, $volume, $user_obj) = @_;
641 if($apputils->check_user_perms(
642 $user_obj->id, $user_obj->home_ou, "UPDATE_VOLUME")) {
643 return OpenILS::Perm->new("UPDATE_VOLUME"); }
645 my $req = $session->request(
646 "open-ils.storage.direct.asset.call_number.update",
648 my $status = $req->gather(1);
653 my($session, $volume, $user_obj) = @_;
655 if($apputils->check_user_perms(
656 $user_obj->id, $user_obj->home_ou, "CREATE_VOLUME")) {
657 return OpenILS::Perm->new("CREATE_VOLUME"); }
659 my $request = $session->request(
660 "open-ils.storage.direct.asset.call_number.create", $volume );
662 my $id = $request->gather(1);
665 OpenILS::Application::AppUtils->rollback_db_session($session);
666 throw OpenSRF::EX::ERROR (" * -> Error creating new volume");
670 warn "received new volume id: $id\n";
678 __PACKAGE__->register_method(
679 method => "fleshed_copy_update",
680 api_name => "open-ils.cat.asset.copy.fleshed.batch.update",
683 sub fleshed_copy_update {
684 my($self, $client, $user_session, $copies) = @_;
686 my $user_obj = $apputils->check_user_session($user_session);
687 my $session = $apputils->start_db_session();
689 for my $copy (@$copies) {
690 _fleshed_copy_update($session, $copy, $user_obj);
693 $apputils->commit_db_session($session);
700 my($session, $copy, $user_obj) = @_;
702 if($apputils->check_user_perms(
703 $user_obj->id, $user_obj->home_ou, "DELETE_COPY")) {
704 return OpenILS::Perm->new("DELETE_COPY"); }
706 warn "Deleting copy " . $copy->id . "\n";
707 my $request = $session->request(
708 "open-ils.storage.direct.asset.copy.delete",
710 return $request->gather(1);
714 my($session, $copy, $user_obj) = @_;
716 if($apputils->check_user_perms(
717 $user_obj->id, $user_obj->home_ou, "CREATE_COPY")) {
718 return OpenILS::Perm->new("CREATE_COPY"); }
720 my $request = $session->request(
721 "open-ils.storage.direct.asset.copy.create",
723 my $id = $request->gather(1);
726 throw OpenSRF::EX::ERROR
727 ("Unable to create new copy " . Dumper($copy));
730 warn "Created copy " . $copy->id . "\n";
737 my($session, $copy, $user_obj) = @_;
739 if($apputils->check_user_perms(
740 $user_obj->id, $user_obj->home_ou, "UPDATE_COPY")) {
741 return OpenILS::Perm->new("UPDATE_COPY"); }
743 my $request = $session->request(
744 "open-ils.storage.direct.asset.copy.update", $copy );
745 my $status = $request->gather(1);
746 warn "Updated copy " . $copy->id . "\n";
751 # -----------------------------------------------------------------
752 # Creates/Updates/Deletes a fleshed asset.copy.
753 # adds/deletes copy stat_cat maps where necessary
754 # -----------------------------------------------------------------
755 sub _fleshed_copy_update {
756 my($session, $copy, $editor) = @_;
758 my $stat_cat_entries = $copy->stat_cat_entries;
759 $copy->editor($editor->id);
761 # in case we're fleshed
762 if(ref($copy->status)) {$copy->status( $copy->status->id ); }
763 if(ref($copy->location)) {$copy->location( $copy->location->id ); }
764 if(ref($copy->circ_lib)) {$copy->circ_lib( $copy->circ_lib->id ); }
766 warn "Updating copy " . Dumper($copy) . "\n";
768 if( $copy->isdeleted ) {
769 return _delete_copy($session, $copy, $editor);
770 } elsif( $copy->isnew ) {
771 $copy = _create_copy($session, $copy, $editor);
772 } elsif( $copy->ischanged ) {
773 _update_copy($session, $copy, $editor);
777 if(!@$stat_cat_entries) { return 1; }
779 my $stat_maps = $session->request(
780 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.search.owning_copy.atomic",
781 $copy->id )->gather(1);
783 if(!$copy->isnew) { _delete_stale_maps($session, $stat_maps, $copy); }
785 # go through the stat cat update/create process
786 for my $stat_entry (@{$stat_cat_entries}){
787 _copy_update_stat_cats( $session, $copy, $stat_maps, $stat_entry, $editor );
794 # -----------------------------------------------------------------
795 # Deletes stat maps attached to this copy in the database that
796 # are no longer attached to the current copy
797 # -----------------------------------------------------------------
798 sub _delete_stale_maps {
799 my( $session, $stat_maps, $copy) = @_;
801 warn "Deleting stale stat maps for copy " . $copy->id . "\n";
802 for my $map (@$stat_maps) {
803 # if there is no stat cat entry on the copy who's id matches the
804 # current map's id, remove the map from the database
805 if(! grep { $_->id == $map->stat_cat_entry } @{$copy->stat_cat_entries} ) {
806 my $req = $session->request(
807 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.delete", $map );
816 # -----------------------------------------------------------------
817 # Searches the stat maps to see if '$entry' already exists on
818 # the given copy. If it does not, a new stat map is created
819 # for the given entry and copy
820 # -----------------------------------------------------------------
821 sub _copy_update_stat_cats {
822 my ( $session, $copy, $stat_maps, $entry, $editor ) = @_;
824 warn "Updating stat maps for copy " . $copy->id . "\n";
826 # see if this map already exists
827 for my $map (@$stat_maps) {
828 if( $map->stat_cat_entry == $entry->id ) {return;}
831 warn "Creating new stat map for stat " .
832 $entry->stat_cat . " and copy " . $copy->id . "\n";
835 my $new_map = Fieldmapper::asset::stat_cat_entry_copy_map->new();
837 $new_map->stat_cat( $entry->stat_cat );
838 $new_map->stat_cat_entry( $entry->id );
839 $new_map->owning_copy( $copy->id );
841 warn "New map is " . Dumper($new_map) . "\n";
843 my $request = $session->request(
844 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.create",
846 my $status = $request->gather(1);
847 warn "created new map with id $status\n";