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);
11 use OpenILS::Utils::Fieldmapper;
15 use Unicode::Normalize;
17 use OpenILS::Utils::FlatXML;
18 use OpenILS::Utils::Editor;
20 use OpenSRF::Utils::SettingsClient;
21 use OpenSRF::Utils::Logger qw($logger);
23 my $apputils = "OpenILS::Application::AppUtils";
25 my $utils = "OpenILS::Application::Cat::Utils";
26 my $U = "OpenILS::Application::AppUtils";
34 my $form = shift || "";
42 $stuff =~ s/([\x{0080}-\x{fffd}])/sprintf('&#x%X;',ord($1))/sgoe;
46 __PACKAGE__->register_method(
47 method => "retrieve_marc_template",
48 api_name => "open-ils.cat.biblio.marc_template.retrieve",
50 Returns a MARC 'record tree' based on a set of pre-defined templates.
51 Templates include : book
54 sub retrieve_marc_template {
55 my( $self, $client, $type ) = @_;
57 return $marctemplates{$type} if defined($marctemplates{$type});
58 $marctemplates{$type} = _load_marc_template($type);
59 return $marctemplates{$type};
62 sub _load_marc_template {
65 if(!$conf) { $conf = OpenSRF::Utils::SettingsClient->new; }
67 my $template = $conf->config_value(
68 "apps", "open-ils.cat","app_settings", "marctemplates", $type );
69 warn "Opening template file $template\n";
71 open( F, $template ) or
72 throw OpenSRF::EX::ERROR ("Unable to open MARC template file: $template : $@");
76 my $xml = join('', @xml);
78 return XML::LibXML->new->parse_string($xml)->documentElement->toString;
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/);
88 __PACKAGE__->register_method(
89 method => "create_record_xml",
90 api_name => "open-ils.cat.biblio.record.xml.create",
92 Inserts a new biblio with the given XML
96 sub create_record_xml {
97 my( $self, $client, $login, $xml, $source ) = @_;
100 my $override = 1 if $self->api_name =~ /override/;
102 my( $user_obj, $evt ) = $U->checksesperm($login, 'CREATE_MARC');
105 $logger->activity("user ".$user_obj->id." creating new MARC record");
107 my $meth = $self->method_lookup("open-ils.cat.biblio.record.xml.import");
109 $meth = $self->method_lookup(
110 "open-ils.cat.biblio.record.xml.import.override") if $override;
112 my ($s) = $meth->run($login, $xml, 2);
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/);
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
133 user_session must have IMPORT_MARC permissions
137 sub biblio_record_xml_import {
138 my( $self, $client, $authtoken, $xml, $source) = @_;
140 my ($tcn, $tcn_source);
142 my $override = 1 if $self->api_name =~ /override/;
144 my( $requestor, $evt ) = $U->checksesperm($authtoken, 'IMPORT_MARC');
147 my $session = $apputils->start_db_session();
150 my $marcxml = XML::LibXML->new->parse_string( $xml );
151 $marcxml->documentElement->setNamespace(
152 "http://www.loc.gov/MARC21/slim", "marc", 1 );
154 my $xpath = '//marc:controlfield[@tag="001"]';
155 $tcn = $marcxml->documentElement->findvalue($xpath);
156 $logger->info("biblio import located 001 (tcn) value of $tcn");
158 $xpath = '//marc:controlfield[@tag="003"]';
159 $tcn_source = $marcxml->documentElement->findvalue($xpath) || "System Local";
161 if(my $rec = _tcn_exists($session, $tcn, $tcn_source)) {
164 $tcn = find_free_tcn( $marcxml, $session );
166 # if we're overriding, try to find a different TCN to use
169 $logger->activity("tcn value $tcn already exists, attempting to override");
172 return OpenILS::Event->new(
173 'OPEN_TCN_NOT_FOUND', payload => $marcxml->toString());
178 $logger->warn("tcn value $origtcn already exists in import/create");
180 # otherwise, return event
181 return OpenILS::Event->new(
182 'TCN_EXISTS', payload => {
191 $logger->activity("user ".$requestor->id.
192 " creating new biblio entry with tcn=$tcn and tcn_source $tcn_source");
196 my $record = Fieldmapper::biblio::record_entry->new;
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 ) );
205 my $id = $session->request(
206 "open-ils.storage.direct.biblio.record_entry.create", $record )->gather(1);
208 return $U->DB_UPDATE_FAILED($record) unless $id;
211 $logger->info("marc create/import created new record $id");
213 $apputils->commit_db_session($session);
215 $logger->debug("Sending record off to be wormized");
217 my $stat = $U->storagereq( 'open-ils.worm.wormize.biblio', $id );
218 throw OpenSRF::EX::ERROR
219 ("Unable to wormize imported record") unless $stat;
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";
236 if(_tcn_exists($session, $tcn, $tcn_source)) {
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;}
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;}
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;}
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;}
271 $marcxml->documentElement->removeChild(
272 $marcxml->documentElement->findnodes( '//datafield[@tag="035"]' )
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 );
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 );
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 );
305 if(!$tcn) {return 0;}
307 $logger->debug("tcn_exists search for tcn $tcn and source $source");
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' } );
313 my $recs = $req->gather(1);
315 if($recs and $recs->[0]) {
316 $logger->debug("_tcn_exists is true for tcn : $tcn ($source)");
320 $logger->debug("_tcn_exists is false for tcn : $tcn ($source)");
326 __PACKAGE__->register_method(
327 method => "biblio_record_tree_retrieve",
328 api_name => "open-ils.cat.biblio.record.tree.retrieve",
331 sub biblio_record_tree_retrieve {
333 my( $self, $client, $recordid ) = @_;
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);
341 throw OpenSRF::EX::ERROR
342 ("No record in database with id $recordid");
345 $session->disconnect();
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 );
353 $tree->owner_doc( $marcxml->id() );
355 warn "returning tree\n";
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
371 sub biblio_record_xml_update {
373 my( $self, $client, $user_session, $id, $xml ) = @_;
375 my $user_obj = $apputils->check_user_session($user_session);
377 if($apputils->check_user_perms(
378 $user_obj->id, $user_obj->home_ou, "UPDATE_MARC")) {
379 return OpenILS::Perm->new("UPDATE_MARC");
382 $logger->activity("user ".$user_obj->id." updating biblio record $id");
385 my $session = OpenILS::Application::AppUtils->start_db_session();
387 warn "Retrieving biblio record from storage for update\n";
389 my $req1 = $session->request(
390 "open-ils.storage.direct.biblio.record_entry.batch.retrieve", $id );
391 my $biblio = $req1->gather(1);
393 warn "retrieved doc $id\n";
395 my $doc = XML::LibXML->new->parse_string($xml);
396 throw OpenSRF::EX::ERROR ("Invalid XML in record update: $xml") unless $doc;
398 $biblio->marc( entityize( $doc->documentElement->toString ) );
399 $biblio->editor( $user_obj->id );
400 $biblio->edit_date( 'now' );
402 warn "Sending updated doc $id to db with xml ".$biblio->marc. "\n";
404 my $req = $session->request(
405 "open-ils.storage.direct.biblio.record_entry.update", $biblio );
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");
416 # Send the doc to the wormer for wormizing
417 warn "Starting worm session\n";
422 my $wreq = $session->request( "open-ils.worm.wormize.biblio", $id );
426 $w = $wreq->gather(1);
430 warn "wormizing failed, rolling back\n";
431 OpenILS::Application::AppUtils->rollback_db_session($session);
433 if($e) { throw $e ($e); }
434 throw OpenSRF::EX::ERROR ("Wormizing Failed for $id" );
437 warn "Committing db session...\n";
438 OpenILS::Application::AppUtils->commit_db_session( $session );
440 # $client->respond_complete($tree);
442 warn "Done wormizing\n";
445 #warn "Returning tree:\n";
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",
462 sub biblio_record_record_metadata {
463 my( $self, $client, $authtoken, $ids ) = @_;
465 return [] unless $ids and @$ids;
467 my $editor = OpenILS::Utils::Editor->new( authtoken => $authtoken );
468 return $editor->event unless $editor->checkauth;
470 my $evt = $U->check_perms(
471 $editor->requestor->id, $editor->requestor->ws_ou, 'VIEW_USER');
477 my $rec = $editor->retrieve_biblio_record_entry($_);
478 $rec->creator($editor->retrieve_actor_user($rec->creator));
479 $rec->editor($editor->retrieve_actor_user($rec->editor));
480 $rec->clear_marc; # slim the record down
481 push( @results, $rec );
489 __PACKAGE__->register_method(
490 method => "biblio_record_marc_cn",
491 api_name => "open-ils.cat.biblio.record.marc_cn.retrieve",
492 argc => 1, #(bib id )
495 sub biblio_record_marc_cn {
496 my( $self, $client, $id ) = @_;
498 my $session = OpenSRF::AppSession->create("open-ils.storage");
500 ->request("open-ils.storage.direct.biblio.record_entry.retrieve", $id )
504 my $doc = XML::LibXML->new->parse_string($marc);
505 $doc->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
508 for my $tag ( qw/050 055 060 070 080 082 086 088 090 092 096 098 099/ ) {
509 my @node = $doc->findnodes("//marc:datafield[\@tag='$tag']");
511 my $cn = $x->findvalue("marc:subfield[\@code='a' or \@code='b']");
512 push @res, {$tag => $cn} if ($cn);
520 #sub _get_userid_by_id {
525 # my $session = OpenSRF::AppSession->create( "open-ils.storage" );
526 # my $request = $session->request(
527 # "open-ils.storage.direct.actor.user.batch.retrieve.atomic", @ids );
529 # $request->wait_complete;
530 # my $response = $request->recv();
531 # if(!$request->complete) { return undef; }
533 # if($response->isa("Error")){
534 # throw $response ($response);
537 # for my $u (@{$response->content}) {
538 # next unless ref($u);
539 # push @users, $u->usrname;
543 # $session->disconnect;
544 # $session->kill_me();
549 sub _get_id_by_userid {
554 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
555 my $request = $session->request(
556 "open-ils.storage.direct.actor.user.search.usrname.atomic", @users );
558 $request->wait_complete;
559 my $response = $request->recv();
560 if(!$request->complete) {
561 throw OpenSRF::EX::ERROR ("no response from storage on user retrieve");
564 if(UNIVERSAL::isa( $response, "Error")){
565 throw $response ($response);
568 for my $u (@{$response->content}) {
574 $session->disconnect;
581 # commits metadata objects to the db
582 sub _update_record_metadata {
584 my ($session, @docs ) = @_;
586 for my $doc (@docs) {
588 my $user_obj = $doc->{user};
589 my $docid = $doc->{docid};
591 warn "Updating metata for doc $docid\n";
593 my $request = $session->request(
594 "open-ils.storage.direct.biblio.record_entry.retrieve", $docid );
595 my $record = $request->gather(1);
597 warn "retrieved record\n";
598 my ($id) = _get_id_by_userid($user_obj->usrname);
600 warn "got $id from _get_id_by_userid\n";
601 $record->editor($id);
603 warn "Grabbed the record, updating and moving on\n";
605 $request = $session->request(
606 "open-ils.storage.direct.biblio.record_entry.update", $record );
610 warn "committing metarecord update\n";
617 __PACKAGE__->register_method(
618 method => "orgs_for_title",
619 api_name => "open-ils.cat.actor.org_unit.retrieve_by_title"
623 my( $self, $client, $record_id ) = @_;
625 my $vols = $apputils->simple_scalar_request(
627 "open-ils.storage.direct.asset.call_number.search_where.atomic",
628 { record => $record_id, deleted => 'f' });
629 #"open-ils.storage.direct.asset.call_number.search.record.atomic",
631 my $orgs = { map {$_->owning_lib => 1 } @$vols };
632 return [ keys %$orgs ];
636 __PACKAGE__->register_method(
637 method => "retrieve_copies",
638 api_name => "open-ils.cat.asset.copy_tree.retrieve");
640 __PACKAGE__->register_method(
641 method => "retrieve_copies",
642 api_name => "open-ils.cat.asset.copy_tree.global.retrieve");
644 # user_session may be null/undef
645 sub retrieve_copies {
647 my( $self, $client, $user_session, $docid, @org_ids ) = @_;
649 if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
653 warn " $$ retrieving copy tree for orgs @org_ids and doc $docid at " . time() . "\n";
655 # grabbing copy trees should be available for everyone..
656 if(!@org_ids and $user_session) {
658 OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
659 @org_ids = ($user_obj->home_ou);
662 if( $self->api_name =~ /global/ ) {
663 warn "performing global copy_tree search for $docid\n";
664 return _build_volume_list( { record => $docid } );
669 for my $orgid (@org_ids) {
670 my $vols = _build_volume_list(
671 { record => $docid, owning_lib => $orgid } );
672 warn "Volumes built for org $orgid\n";
673 push( @all_vols, @$vols );
676 warn " $$ Finished copy_tree at " . time() . "\n";
684 sub _build_volume_list {
685 my $search_hash = shift;
687 $search_hash->{deleted} = 'f';
689 my $session = OpenSRF::AppSession->create( "open-ils.storage" );
692 my $request = $session->request(
693 "open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
694 #"open-ils.storage.direct.asset.call_number.search.atomic", $search_hash );
696 my $vols = $request->gather(1);
699 for my $volume (@$vols) {
701 warn "Grabbing copies for volume: " . $volume->id . "\n";
702 my $creq = $session->request(
703 "open-ils.storage.direct.asset.copy.search_where.atomic",
704 { call_number => $volume->id , deleted => 'f' });
705 #"open-ils.storage.direct.asset.copy.search.call_number.atomic", $volume->id );
707 my $copies = $creq->gather(1);
709 $copies = [ sort { $a->barcode cmp $b->barcode } @$copies ];
711 $volume->copies($copies);
713 push( @volumes, $volume );
717 $session->disconnect();
725 # -----------------------------------------------------------------
726 # Fleshed volume tree batch add/update. This does everything a
727 # volume tree could want, add, update, delete
728 # -----------------------------------------------------------------
729 __PACKAGE__->register_method(
730 method => "volume_tree_fleshed_update",
731 api_name => "open-ils.cat.asset.volume_tree.fleshed.batch.update",
733 sub volume_tree_fleshed_update {
735 my( $self, $client, $user_session, $volumes ) = @_;
736 return undef unless $volumes;
738 my $user_obj = $apputils->check_user_session($user_session);
740 # first lets see if we have any dup volume labels
742 # my @volumes = @$volumes;
743 # for my $vol (@volumes) {
744 # $volumes{$_->owning_lib} = {} unless $volumes{$_->owning_lib};
745 # $volumes{$_->record} = {} unless $volumes{$_->record};
746 # if($volumes{$_->record}->{$_->label}) {
747 # $logger->info("Duplicate volume label found ".
748 # "for record ".$_->record." and label ".$_->label);
749 # $volumes{$_->record}->{$_->label}++;
751 # $volumes{$_->record}->{$_->label} = 1;
755 # for my $r (values %volumes) {
756 # for my $l (keys %{$volumes{$r}}) {
757 # return OpenILS::Event->new('CALL_NUMBER_EXISTS', payload => $l)
758 # if $volumes{$r}{$l} > 1;
763 my $session = $apputils->start_db_session();
764 warn "Looping on volumes in fleshed volume tree update\n";
766 # cycle through the volumes provided and update/create/delete where necessary
767 for my $volume (@$volumes) {
769 warn "updating volume " . $volume->id . "\n";
771 my $update_copy_list = $volume->copies;
774 if( $volume->isdeleted) {
775 my $status = _delete_volume($session, $volume, $user_obj);
777 #throw OpenSRF::EX::ERROR
778 #("Volume delete failed for volume " . $volume->id);
780 if(UNIVERSAL::isa($status, "Fieldmapper::perm_ex")) { return $status; }
782 } elsif( $volume->isnew ) {
785 $volume->editor($user_obj->id);
786 $volume->creator($user_obj->id);
787 $volume = _add_volume($session, $volume, $user_obj);
790 if($volume and UNIVERSAL::isa($volume, "Fieldmapper::perm_ex")) { return $volume; }
792 } elsif( $volume->ischanged ) {
794 $volume->editor($user_obj->id);
795 my $stat = _update_volume($session, $volume, $user_obj);
796 if($stat and UNIVERSAL::isa($stat, "Fieldmapper::perm_ex")) { return $stat; }
800 if( ! $volume->isdeleted ) {
801 for my $copy (@{$update_copy_list}) {
803 $copy->editor($user_obj->id);
804 warn "updating copy for volume " . $volume->id . "\n";
809 $copy->call_number($volume->id);
810 $copy->creator($user_obj->id);
811 $copy = _fleshed_copy_update($session,$copy,$user_obj);
813 } elsif( $copy->ischanged ) {
814 $copy->call_number($volume->id);
815 $copy = _fleshed_copy_update($session, $copy, $user_obj);
817 } elsif( $copy->isdeleted ) {
818 warn "Deleting copy " . $copy->id . " for volume " . $volume->id . "\n";
819 my $status = _fleshed_copy_update($session, $copy, $user_obj);
820 warn "Copy delete returned a status of $status\n";
826 $apputils->commit_db_session($session);
827 return scalar(@$volumes);
832 my( $session, $volume, $user_obj ) = @_;
834 if($apputils->check_user_perms(
835 $user_obj->id, $user_obj->home_ou, "DELETE_VOLUME")) {
836 return OpenILS::Perm->new("DELETE_VOLUME"); }
838 #$volume = _find_volume($session, $volume);
839 warn "Deleting volume " . $volume->id . "\n";
841 my $copies = $session->request(
842 "open-ils.storage.direct.asset.copy.search_where.atomic",
843 { call_number => $volume->id, deleted => 'f' } )->gather(1);
844 #"open-ils.storage.direct.asset.copy.search.call_number.atomic",
847 throw OpenSRF::EX::ERROR
848 ("Cannot remove volume with copies attached");
851 my $req = $session->request(
852 "open-ils.storage.direct.asset.call_number.delete",
854 return $req->gather(1);
859 my($session, $volume, $user_obj) = @_;
860 if($apputils->check_user_perms(
861 $user_obj->id, $user_obj->home_ou, "UPDATE_VOLUME")) {
862 return OpenILS::Perm->new("UPDATE_VOLUME"); }
864 my $req = $session->request(
865 "open-ils.storage.direct.asset.call_number.update",
867 my $status = $req->gather(1);
872 my($session, $volume, $user_obj) = @_;
874 if($apputils->check_user_perms(
875 $user_obj->id, $user_obj->home_ou, "CREATE_VOLUME")) {
876 warn "User does not have priveleges to create new volumes\n";
877 return OpenILS::Perm->new("CREATE_VOLUME");
880 my $request = $session->request(
881 "open-ils.storage.direct.asset.call_number.create", $volume );
883 my $id = $request->gather(1);
886 OpenILS::Application::AppUtils->rollback_db_session($session);
887 throw OpenSRF::EX::ERROR (" * -> Error creating new volume");
891 warn "received new volume id: $id\n";
901 __PACKAGE__->register_method(
902 method => "fleshed_copy_update",
903 api_name => "open-ils.cat.asset.copy.fleshed.batch.update",);
905 __PACKAGE__->register_method(
906 method => "fleshed_copy_update",
907 api_name => "open-ils.cat.asset.copy.fleshed.batch.update.override",);
909 sub fleshed_copy_update {
910 my( $self, $conn, $auth, $copies ) = @_;
911 my( $reqr, $evt ) = $U->checkses($auth);
913 my $editor = OpenILS::Utils::Editor->new( requestor => $reqr, xact => 1 );
914 $evt = update_fleshed_copies(
915 $editor, ($self->api_name =~ /override/), undef, $copies);
918 $logger->info("fleshed copy update successfully updated ".scalar(@$copies)." copies");
926 my($session, $copy, $user_obj) = @_;
928 if($apputils->check_user_perms(
929 $user_obj->id, $user_obj->home_ou, "DELETE_COPY")) {
930 return OpenILS::Perm->new("DELETE_COPY"); }
932 warn "Deleting copy " . $copy->id . "\n";
933 my $request = $session->request(
934 "open-ils.storage.direct.asset.copy.delete",
936 return $request->gather(1);
940 my($session, $copy, $user_obj) = @_;
942 if($apputils->check_user_perms(
943 $user_obj->id, $user_obj->home_ou, "CREATE_COPY")) {
944 return OpenILS::Perm->new("CREATE_COPY"); }
946 my $request = $session->request(
947 "open-ils.storage.direct.asset.copy.create",
949 my $id = $request->gather(1);
952 throw OpenSRF::EX::ERROR
953 ("Unable to create new copy " . Dumper($copy));
956 warn "Created copy " . $copy->id . "\n";
963 my($session, $copy, $user_obj) = @_;
965 my $evt = $apputils->check_perms($user_obj->id, $copy->circ_lib, 'UPDATE_COPY');
966 return $evt if $evt; #XXX NOT YET HANDLED BY CALLER
968 my $status = $apputils->simplereq(
970 "open-ils.storage.direct.asset.copy.update", $copy );
971 $logger->debug("Successfully updated copy " . $copy->id );
977 # -----------------------------------------------------------------
978 # Creates/Updates/Deletes a fleshed asset.copy.
979 # adds/deletes copy stat_cat maps where necessary
980 # -----------------------------------------------------------------
981 sub _fleshed_copy_update {
982 my($session, $copy, $editor) = @_;
984 my $stat_cat_entries = $copy->stat_cat_entries;
985 $copy->editor($editor->id);
987 # in case we're fleshed
988 if(ref($copy->status)) {$copy->status( $copy->status->id ); }
989 if(ref($copy->location)) {$copy->location( $copy->location->id ); }
990 if(ref($copy->circ_lib)) {$copy->circ_lib( $copy->circ_lib->id ); }
992 warn "Updating copy " . Dumper($copy) . "\n";
994 if( $copy->isdeleted ) {
995 return _delete_copy($session, $copy, $editor);
996 } elsif( $copy->isnew ) {
997 $copy = _create_copy($session, $copy, $editor);
998 } elsif( $copy->ischanged ) {
999 _update_copy($session, $copy, $editor);
1003 return 1 unless ( $stat_cat_entries and @$stat_cat_entries );
1005 my $stat_maps = $session->request(
1006 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.search.owning_copy.atomic",
1007 $copy->id )->gather(1);
1009 if(!$copy->isnew) { _delete_stale_maps($session, $stat_maps, $copy); }
1011 # go through the stat cat update/create process
1012 for my $stat_entry (@{$stat_cat_entries}){
1013 _copy_update_stat_cats( $session, $copy, $stat_maps, $stat_entry, $editor );
1020 # -----------------------------------------------------------------
1021 # Deletes stat maps attached to this copy in the database that
1022 # are no longer attached to the current copy
1023 # -----------------------------------------------------------------
1024 sub _delete_stale_maps {
1025 my( $session, $stat_maps, $copy) = @_;
1027 warn "Deleting stale stat maps for copy " . $copy->id . "\n";
1028 for my $map (@$stat_maps) {
1029 # if there is no stat cat entry on the copy who's id matches the
1030 # current map's id, remove the map from the database
1031 if(! grep { $_->id == $map->stat_cat_entry } @{$copy->stat_cat_entries} ) {
1032 my $req = $session->request(
1033 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.delete", $map );
1042 # -----------------------------------------------------------------
1043 # Searches the stat maps to see if '$entry' already exists on
1044 # the given copy. If it does not, a new stat map is created
1045 # for the given entry and copy
1046 # -----------------------------------------------------------------
1047 sub _copy_update_stat_cats {
1048 my ( $session, $copy, $stat_maps, $entry, $editor ) = @_;
1050 warn "Updating stat maps for copy " . $copy->id . "\n";
1052 # see if this map already exists
1053 for my $map (@$stat_maps) {
1054 if( $map->stat_cat_entry == $entry->id ) {return;}
1057 warn "Creating new stat map for stat " .
1058 $entry->stat_cat . " and copy " . $copy->id . "\n";
1061 my $new_map = Fieldmapper::asset::stat_cat_entry_copy_map->new();
1063 $new_map->stat_cat( $entry->stat_cat );
1064 $new_map->stat_cat_entry( $entry->id );
1065 $new_map->owning_copy( $copy->id );
1067 warn "New map is " . Dumper($new_map) . "\n";
1069 my $request = $session->request(
1070 "open-ils.storage.direct.asset.stat_cat_entry_copy_map.create",
1072 my $status = $request->gather(1);
1073 warn "created new map with id $status\n";
1080 __PACKAGE__->register_method(
1082 api_name => 'open-ils.cat.biblio.records.merge',
1084 Merges a group of records
1085 @param auth The login session key
1086 @param master The id of the record all other r
1087 ecords should be merged into
1088 @param records Array of records to be merged into the master record
1089 @return 1 on success, Event on error.
1094 my( $self, $conn, $auth, $master, $records ) = @_;
1095 my( $reqr, $evt ) = $U->checkses($auth);
1096 return $evt if $evt;
1097 my %r = map { $_ => 1 } (@$records, $master); # unique the ids
1098 $records = [ keys %r ];
1099 my $editor = OpenILS::Utils::Editor->new( requestor => $reqr, xact => 1 );
1100 my $v = OpenILS::Application::Cat::Merge::merge_records($editor, $master, $records);
1109 # ---------------------------------------------------------------------------
1110 # ---------------------------------------------------------------------------
1112 # returns true if the given title (id) has no un-deleted
1114 sub title_is_empty {
1115 my( $editor, $rid ) = @_;
1116 my $cnlist = $editor->search_asset_call_number(
1117 { record => $rid, deleted => 'f' }, { idlist => 1 } );
1118 return 1 unless @$cnlist;
1120 for my $cn (@$cnlist) {
1121 my $copylist = $editor->search_asset_copy(
1122 { call_number => $cn, deleted => 'f' }, { idlist => 1 });
1123 return 0 if @$copylist;
1130 __PACKAGE__->register_method(
1131 method => "fleshed_volume_update",
1132 api_name => "open-ils.cat.asset.volume.fleshed.batch.update",);
1134 __PACKAGE__->register_method(
1135 method => "fleshed_volume_update",
1136 api_name => "open-ils.cat.asset.volume.fleshed.batch.update.override",);
1138 sub fleshed_volume_update {
1139 my( $self, $conn, $auth, $volumes ) = @_;
1140 my( $reqr, $evt ) = $U->checkses($auth);
1141 return $evt if $evt;
1143 my $override = ($self->api_name =~ /override/);
1144 my $editor = OpenILS::Utils::Editor->new( requestor => $reqr, xact => 1 );
1146 for my $vol (@$volumes) {
1147 $logger->info("vol-update: investigating volume ".$vol->id);
1149 $vol->editor($reqr->id);
1150 $vol->edit_date('now');
1152 my $copies = $vol->copies;
1155 if( $vol->isdeleted ) {
1157 $logger->info("vol-update: deleting volume");
1158 my $cs = $editor->search_asset_copy(
1159 { call_number => $vol->id, deleted => 'f' } );
1160 return OpenILS::Event->new(
1161 'VOLUME_NOT_EMPTY', payload => $vol->id ) if @$cs;
1164 $evt = $editor->update_asset_call_number($vol);
1165 return $evt if $evt;
1168 } elsif( $vol->isnew ) {
1169 $logger->info("vol-update: creating volume");
1170 $evt = create_volume( $override, $editor, $vol );
1171 return $evt if $evt;
1173 } elsif( $vol->ischanged ) {
1174 $logger->info("vol-update: update volume");
1175 $evt = $editor->update_asset_call_number($vol);
1176 return $evt if $evt;
1179 # now update any attached copies
1180 if( @$copies and !$vol->isdeleted ) {
1181 $_->call_number($vol->id) for @$copies;
1182 $evt = update_fleshed_copies( $editor, $override, $vol, $copies );
1183 return $evt if $evt;
1188 return scalar(@$volumes);
1192 # this does the actual work
1193 sub update_fleshed_copies {
1194 my( $editor, $override, $vol, $copies ) = @_;
1197 my $fetchvol = ($vol) ? 0 : 1;
1200 $cache{$vol->id} = $vol if $vol;
1202 for my $copy (@$copies) {
1204 my $copyid = $copy->id;
1205 $logger->info("vol-update: inspecting copy $copyid");
1207 if( !($vol = $cache{$copy->call_number}) ) {
1208 $vol = $cache{$copy->call_number} =
1209 $editor->retrieve_asset_call_number($copy->call_number);
1212 $copy->editor($editor->requestor->id);
1213 $copy->edit_date('now');
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);
1219 my $sc_entries = $copy->stat_cat_entries;
1220 $copy->clear_stat_cat_entries;
1222 if( $copy->isdeleted ) {
1223 $evt = delete_copy($editor, $override, $vol, $copy);
1224 return $evt if $evt;
1226 } elsif( $copy->isnew ) {
1227 $evt = create_copy( $editor, $vol, $copy );
1228 return $evt if $evt;
1230 } elsif( $copy->ischanged ) {
1232 $logger->info("vol-update: updating copy $copyid");
1233 $evt = $editor->update_asset_copy(
1234 $copy, {checkperm=>1, org=>$vol->owning_lib});
1235 return $evt if $evt;
1238 $copy->stat_cat_entries( $sc_entries );
1239 $evt = update_copy_stat_entries($editor, $copy);
1240 return $evt if $evt;
1243 $logger->debug("vol-update: done updating copy batch");
1249 my( $editor, $override, $vol, $copy ) = @_;
1251 $logger->info("vol-update: deleting copy ".$copy->id);
1252 $copy->deleted('t');
1254 my $evt = $editor->update_asset_copy(
1255 $copy, {checkperm=>1, org=>$vol->owning_lib});
1256 return $evt if $evt;
1258 if( title_is_empty($editor, $vol->record) ) {
1261 my $rec = $editor->retrieve_biblio_record_entry($vol->record);
1263 $evt = $editor->update_biblio_record_entry($rec, {checkperm=>0});
1264 return $evt if $evt;
1267 return OpenILS::Event->new('TITLE_LAST_COPY');
1275 my( $editor, $vol, $copy ) = @_;
1277 my $existing = $editor->search_asset_copy(
1278 { barcode => $copy->barcode } );
1280 return OpenILS::Event->new('ITEM_BARCODE_EXISTS') if @$existing;
1283 $copy->creator($editor->requestor->id);
1284 $copy->create_date('now');
1286 my $evt = $editor->create_asset_copy(
1287 $copy, {checkperm=>1, org=>$vol->owning_lib});
1288 return $evt if $evt;
1290 $copy->id($editor->lastid);
1294 sub update_copy_stat_entries {
1295 my( $editor, $copy ) = @_;
1298 my $entries = $copy->stat_cat_entries;
1299 return undef unless ($entries and @$entries);
1301 my $maps = $editor->search_asset_stat_cat_entry_copy_map({copy=>$copy->id});
1304 # if there is no stat cat entry on the copy who's id matches the
1305 # current map's id, remove the map from the database
1306 for my $map (@$maps) {
1307 if(! grep { $_->id == $map->stat_cat_entry } @$entries ) {
1309 $logger->info("copy update found stale ".
1310 "stat cat entry map ".$map->id. " on copy ".$copy->id);
1312 $evt = $editor->delete_asset_stat_cat_entry_copy_map($map);
1313 return $evt if $evt;
1318 # go through the stat cat update/create process
1319 for my $entry (@$entries) {
1321 # if this link already exists in the DB, don't attempt to re-create it
1322 next if( grep{$_->stat_cat_entry == $entry->id} @$maps );
1324 my $new_map = Fieldmapper::asset::stat_cat_entry_copy_map->new();
1326 $new_map->stat_cat( $entry->stat_cat );
1327 $new_map->stat_cat_entry( $entry->id );
1328 $new_map->owning_copy( $copy->id );
1330 $evt = $editor->create_asset_stat_cat_entry_copy_map($new_map);
1331 return $evt if $evt;
1333 $logger->info("copy update created new stat cat entry map ".$editor->lastid);
1341 my( $override, $editor, $vol ) = @_;
1345 # first lets see if there are any collisions
1346 my $vols = $editor->search_asset_call_number( {
1347 owning_lib => $vol->owning_lib,
1348 record => $vol->record,
1349 label => $vol->label
1356 $label = $vol->label;
1358 return OpenILS::Event->new(
1359 'VOLUME_LABEL_EXISTS', payload => $vol->id);
1363 # create a temp label so we can create the volume, then de-dup it
1364 $vol->label( '__SYSTEM_TMP_'.time) if $label;
1366 $vol->creator($editor->requestor->id);
1367 $vol->create_date('now');
1370 $evt = $editor->create_asset_call_number($vol);
1371 return $evt if $evt;
1372 $vol->id($editor->lastid);
1376 # now restore the label and merge into the existing record
1377 $vol->label($label);
1379 OpenILS::Application::Cat::Merge::merge_volumes($editor, [$vol], $$vols[0]);
1380 return $evt if $evt;