]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Cat.pm
more moving of code out to the common packages
[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 OpenILS::Application;
5 use OpenILS::Application::Cat::Merge;
6 use OpenILS::Application::Cat::Authority;
7 use OpenILS::Application::Cat::BibCommon;
8 use OpenILS::Application::Cat::AssetCommon;
9 use base qw/OpenILS::Application/;
10 use Time::HiRes qw(time);
11 use OpenSRF::EX qw(:try);
12 use OpenSRF::Utils::JSON;
13 use OpenILS::Utils::Fieldmapper;
14 use OpenILS::Event;
15 use OpenILS::Const qw/:const/;
16
17 use XML::LibXML;
18 use Unicode::Normalize;
19 use Data::Dumper;
20 use OpenILS::Utils::FlatXML;
21 use OpenILS::Utils::CStoreEditor q/:funcs/;
22 use OpenILS::Perm;
23 use OpenSRF::Utils::SettingsClient;
24 use OpenSRF::Utils::Logger qw($logger);
25 use OpenSRF::AppSession;
26
27 my $U = "OpenILS::Application::AppUtils";
28 my $conf;
29 my %marctemplates;
30
31 __PACKAGE__->register_method(
32         method  => "retrieve_marc_template",
33         api_name        => "open-ils.cat.biblio.marc_template.retrieve",
34         notes           => <<"  NOTES");
35         Returns a MARC 'record tree' based on a set of pre-defined templates.
36         Templates include : book
37         NOTES
38
39 sub retrieve_marc_template {
40         my( $self, $client, $type ) = @_;
41         return $marctemplates{$type} if defined($marctemplates{$type});
42         $marctemplates{$type} = _load_marc_template($type);
43         return $marctemplates{$type};
44 }
45
46 __PACKAGE__->register_method(
47         method => 'fetch_marc_template_types',
48         api_name => 'open-ils.cat.marc_template.types.retrieve'
49 );
50
51 my $marc_template_files;
52
53 sub fetch_marc_template_types {
54         my( $self, $conn ) = @_;
55         __load_marc_templates();
56         return [ keys %$marc_template_files ];
57 }
58
59 sub __load_marc_templates {
60         return if $marc_template_files;
61         if(!$conf) { $conf = OpenSRF::Utils::SettingsClient->new; }
62
63         $marc_template_files = $conf->config_value(                                     
64                 "apps", "open-ils.cat","app_settings", "marctemplates" );
65
66         $logger->info("Loaded marc templates: " . Dumper($marc_template_files));
67 }
68
69 sub _load_marc_template {
70         my $type = shift;
71
72         __load_marc_templates();
73
74         my $template = $$marc_template_files{$type};
75         open( F, $template ) or 
76                 throw OpenSRF::EX::ERROR ("Unable to open MARC template file: $template : $@");
77
78         my @xml = <F>;
79         close(F);
80         my $xml = join('', @xml);
81
82         return XML::LibXML->new->parse_string($xml)->documentElement->toString;
83 }
84
85
86
87 __PACKAGE__->register_method(
88         method => 'fetch_bib_sources',
89         api_name => 'open-ils.cat.bib_sources.retrieve.all');
90
91 sub fetch_bib_sources {
92         return OpenILS::Application::Cat::BibCommon->fetch_bib_sources();
93 }
94
95 __PACKAGE__->register_method(
96         method  => "create_record_xml",
97         api_name        => "open-ils.cat.biblio.record.xml.create.override",
98         signature       => q/@see open-ils.cat.biblio.record.xml.create/);
99
100 __PACKAGE__->register_method(
101         method          => "create_record_xml",
102         api_name                => "open-ils.cat.biblio.record.xml.create",
103         signature       => q/
104                 Inserts a new biblio with the given XML
105         /
106 );
107
108 sub create_record_xml {
109         my( $self, $client, $login, $xml, $source ) = @_;
110
111         my $override = 1 if $self->api_name =~ /override/;
112
113         my( $user_obj, $evt ) = $U->checksesperm($login, 'CREATE_MARC');
114         return $evt if $evt;
115
116         $logger->activity("user ".$user_obj->id." creating new MARC record");
117
118         my $meth = $self->method_lookup("open-ils.cat.biblio.record.xml.import");
119
120         $meth = $self->method_lookup(
121                 "open-ils.cat.biblio.record.xml.import.override") if $override;
122
123         my ($s) = $meth->run($login, $xml, $source);
124         return $s;
125 }
126
127
128
129 __PACKAGE__->register_method(
130         method  => "biblio_record_replace_marc",
131         api_name        => "open-ils.cat.biblio.record.xml.update",
132         argc            => 3, 
133         signature       => q/
134                 Updates the XML for a given biblio record.
135                 This does not change any other aspect of the record entry
136                 exception the XML, the editor, and the edit date.
137                 @return The update record object
138         /
139 );
140
141 __PACKAGE__->register_method(
142         method          => 'biblio_record_replace_marc',
143         api_name                => 'open-ils.cat.biblio.record.marc.replace',
144         signature       => q/
145                 @param auth The authtoken
146                 @param recid The record whose MARC we're replacing
147                 @param newxml The new xml to use
148         /
149 );
150
151 __PACKAGE__->register_method(
152         method          => 'biblio_record_replace_marc',
153         api_name                => 'open-ils.cat.biblio.record.marc.replace.override',
154         signature       => q/@see open-ils.cat.biblio.record.marc.replace/
155 );
156
157 sub biblio_record_replace_marc  {
158         my( $self, $conn, $auth, $recid, $newxml, $source ) = @_;
159         my $e = new_editor(authtoken=>$auth, xact=>1);
160         return $e->die_event unless $e->checkauth;
161         return $e->die_event unless $e->allowed('CREATE_MARC', $e->requestor->ws_ou);
162
163     my $res = OpenILS::Application::Cat::BibCommon->biblio_record_replace_marc(
164         $e, $recid, $newxml, $source, 
165         $self->api_name =~ /replace/o,
166         $self->api_name =~ /override/o);
167
168     $e->commit unless $U->event_code($res);
169     return $res;
170 }
171
172 __PACKAGE__->register_method(
173         method  => "update_biblio_record_entry",
174         api_name        => "open-ils.cat.biblio.record_entry.update",
175     signature => q/
176         Updates a biblio.record_entry
177         @param auth The authtoken
178         @param record The record with updated values
179         @return 1 on success, Event on error.
180     /
181 );
182
183 sub update_biblio_record_entry {
184     my($self, $conn, $auth, $record) = @_;
185     my $e = new_editor(authtoken=>$auth, xact=>1);
186     return $e->die_event unless $e->checkauth;
187     return $e->die_event unless $e->allowed('UPDATE_RECORD');
188     $e->update_biblio_record_entry($record) or return $e->die_event;
189     $e->commit;
190     return 1;
191 }
192
193 __PACKAGE__->register_method(
194         method  => "undelete_biblio_record_entry",
195         api_name        => "open-ils.cat.biblio.record_entry.undelete",
196     signature => q/
197         Un-deletes a record and sets active=true
198         @param auth The authtoken
199         @param record The record_id to ressurect
200         @return 1 on success, Event on error.
201     /
202 );
203 sub undelete_biblio_record_entry {
204     my($self, $conn, $auth, $record_id) = @_;
205     my $e = new_editor(authtoken=>$auth, xact=>1);
206     return $e->die_event unless $e->checkauth;
207     return $e->die_event unless $e->allowed('UPDATE_RECORD');
208
209     my $record = $e->retrieve_biblio_record_entry($record_id)
210         or return $e->die_event;
211     $record->deleted('f');
212     $record->active('t');
213
214     # no 2 non-deleted records can have the same tcn_value
215     my $existing = $e->search_biblio_record_entry(
216         {   deleted => 'f', 
217             tcn_value => $record->tcn_value, 
218             id => {'!=' => $record_id}
219         }, {idlist => 1});
220     return OpenILS::Event->new('TCN_EXISTS') if @$existing;
221
222     $e->update_biblio_record_entry($record) or return $e->die_event;
223     $e->commit;
224     return 1;
225 }
226
227
228 __PACKAGE__->register_method(
229         method  => "biblio_record_xml_import",
230         api_name        => "open-ils.cat.biblio.record.xml.import.override",
231         signature       => q/@see open-ils.cat.biblio.record.xml.import/);
232
233 __PACKAGE__->register_method(
234         method  => "biblio_record_xml_import",
235         api_name        => "open-ils.cat.biblio.record.xml.import",
236         notes           => <<"  NOTES");
237         Takes a marcxml record and imports the record into the database.  In this
238         case, the marcxml record is assumed to be a complete record (i.e. valid
239         MARC).  The title control number is taken from (whichever comes first)
240         tags 001, 039[ab], 020a, 022a, 010, 035a and whichever does not already exist
241         in the database.
242         user_session must have IMPORT_MARC permissions
243         NOTES
244
245
246 sub biblio_record_xml_import {
247         my( $self, $client, $authtoken, $xml, $source, $auto_tcn) = @_;
248     my $e = new_editor(xact=>1, authtoken=>$authtoken);
249     return $e->die_event unless $e->checkauth;
250     return $e->die_event unless $e->allowed('IMPORT_MARC', $e->requestor->ws_ou);
251
252     my $res = OpenILS::Application::Cat::BibCommon->biblio_record_xml_import(
253         $e, $xml, $source, $auto_tcn, $self->api_name =~ /override/);
254
255     $e->commit unless $U->event_code($res);
256     return $res;
257 }
258
259 __PACKAGE__->register_method(
260         method  => "biblio_record_record_metadata",
261         api_name        => "open-ils.cat.biblio.record.metadata.retrieve",
262     authoritative => 1,
263         argc            => 1, #(session_id, biblio_tree ) 
264         notes           => "Walks the tree and commits any changed nodes " .
265                                         "adds any new nodes, and deletes any deleted nodes",
266 );
267
268 sub biblio_record_record_metadata {
269         my( $self, $client, $authtoken, $ids ) = @_;
270
271         return [] unless $ids and @$ids;
272
273         my $editor = new_editor(authtoken => $authtoken);
274         return $editor->event unless $editor->checkauth;
275         return $editor->event unless $editor->allowed('VIEW_USER');
276
277         my @results;
278
279         for(@$ids) {
280                 return $editor->event unless 
281                         my $rec = $editor->retrieve_biblio_record_entry($_);
282                 $rec->creator($editor->retrieve_actor_user($rec->creator));
283                 $rec->editor($editor->retrieve_actor_user($rec->editor));
284                 $rec->clear_marc; # slim the record down
285                 push( @results, $rec );
286         }
287
288         return \@results;
289 }
290
291
292
293 __PACKAGE__->register_method(
294         method  => "biblio_record_marc_cn",
295         api_name        => "open-ils.cat.biblio.record.marc_cn.retrieve",
296         argc            => 1, #(bib id ) 
297 );
298
299 sub biblio_record_marc_cn {
300         my( $self, $client, $id ) = @_;
301
302         my $session = OpenSRF::AppSession->create("open-ils.cstore");
303         my $marc = $session
304                 ->request("open-ils.cstore.direct.biblio.record_entry.retrieve", $id )
305                 ->gather(1)
306                 ->marc;
307
308         my $doc = XML::LibXML->new->parse_string($marc);
309         $doc->documentElement->setNamespace( "http://www.loc.gov/MARC21/slim", "marc", 1 );
310         
311         my @res;
312         for my $tag ( qw/050 055 060 070 080 082 086 088 090 092 096 098 099/ ) {
313                 my @node = $doc->findnodes("//marc:datafield[\@tag='$tag']");
314                 for my $x (@node) {
315                         my $cn = $x->findvalue("marc:subfield[\@code='a' or \@code='b']");
316                         push @res, {$tag => $cn} if ($cn);
317                 }
318         }
319
320         return \@res
321 }
322
323
324 __PACKAGE__->register_method(
325         method  => "orgs_for_title",
326     authoritative => 1,
327         api_name        => "open-ils.cat.actor.org_unit.retrieve_by_title"
328 );
329
330 sub orgs_for_title {
331         my( $self, $client, $record_id ) = @_;
332
333         my $vols = $U->simple_scalar_request(
334                 "open-ils.cstore",
335                 "open-ils.cstore.direct.asset.call_number.search.atomic",
336                 { record => $record_id, deleted => 'f' });
337
338         my $orgs = { map {$_->owning_lib => 1 } @$vols };
339         return [ keys %$orgs ];
340 }
341
342
343 __PACKAGE__->register_method(
344         method  => "retrieve_copies",
345     authoritative => 1,
346         api_name        => "open-ils.cat.asset.copy_tree.retrieve");
347
348 __PACKAGE__->register_method(
349         method  => "retrieve_copies",
350         api_name        => "open-ils.cat.asset.copy_tree.global.retrieve");
351
352 # user_session may be null/undef
353 sub retrieve_copies {
354
355         my( $self, $client, $user_session, $docid, @org_ids ) = @_;
356
357         if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
358
359         $docid = "$docid";
360
361         # grabbing copy trees should be available for everyone..
362         if(!@org_ids and $user_session) {
363                 my $user_obj = 
364                         OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
365                         @org_ids = ($user_obj->home_ou);
366         }
367
368         if( $self->api_name =~ /global/ ) {
369                 return _build_volume_list( { record => $docid, deleted => 'f' } );
370
371         } else {
372
373                 my @all_vols;
374                 for my $orgid (@org_ids) {
375                         my $vols = _build_volume_list( 
376                                         { record => $docid, owning_lib => $orgid, deleted => 'f' } );
377                         push( @all_vols, @$vols );
378                 }
379                 
380                 return \@all_vols;
381         }
382
383         return undef;
384 }
385
386
387 sub _build_volume_list {
388         my $search_hash = shift;
389
390         $search_hash->{deleted} = 'f';
391         my $e = new_editor();
392
393         my $vols = $e->search_asset_call_number($search_hash);
394
395         my @volumes;
396
397         for my $volume (@$vols) {
398
399                 my $copies = $e->search_asset_copy(
400                         { call_number => $volume->id , deleted => 'f' });
401
402                 $copies = [ sort { $a->barcode cmp $b->barcode } @$copies  ];
403
404                 for my $c (@$copies) {
405                         if( $c->status == OILS_COPY_STATUS_CHECKED_OUT ) {
406                                 $c->circulations(
407                                         $e->search_action_circulation(
408                                                 [
409                                                         { target_copy => $c->id },
410                                                         {
411                                                                 order_by => { circ => 'xact_start desc' },
412                                                                 limit => 1
413                                                         }
414                                                 ]
415                                         )
416                                 )
417                         }
418                 }
419
420                 $volume->copies($copies);
421                 push( @volumes, $volume );
422         }
423
424         #$session->disconnect();
425         return \@volumes;
426
427 }
428
429
430 __PACKAGE__->register_method(
431         method  => "fleshed_copy_update",
432         api_name        => "open-ils.cat.asset.copy.fleshed.batch.update",);
433
434 __PACKAGE__->register_method(
435         method  => "fleshed_copy_update",
436         api_name        => "open-ils.cat.asset.copy.fleshed.batch.update.override",);
437
438
439 sub fleshed_copy_update {
440         my( $self, $conn, $auth, $copies, $delete_stats ) = @_;
441         return 1 unless ref $copies;
442         my( $reqr, $evt ) = $U->checkses($auth);
443         return $evt if $evt;
444         my $editor = new_editor(requestor => $reqr, xact => 1);
445         my $override = $self->api_name =~ /override/;
446         $evt = OpenILS::Application::Cat::AssetCommon->update_fleshed_copies(
447         $editor, $override, undef, $copies, $delete_stats);
448         if( $evt ) { 
449                 $logger->info("fleshed copy update failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
450                 $editor->rollback; 
451                 return $evt; 
452         }
453         $editor->commit;
454         $logger->info("fleshed copy update successfully updated ".scalar(@$copies)." copies");
455         return 1;
456 }
457
458
459 __PACKAGE__->register_method(
460         method => 'merge',
461         api_name        => 'open-ils.cat.biblio.records.merge',
462         signature       => q/
463                 Merges a group of records
464                 @param auth The login session key
465                 @param master The id of the record all other records should be merged into
466                 @param records Array of records to be merged into the master record
467                 @return 1 on success, Event on error.
468         /
469 );
470
471 sub merge {
472         my( $self, $conn, $auth, $master, $records ) = @_;
473         my( $reqr, $evt ) = $U->checkses($auth);
474         return $evt if $evt;
475         my $editor = new_editor( requestor => $reqr, xact => 1 );
476         my $v = OpenILS::Application::Cat::Merge::merge_records($editor, $master, $records);
477         return $v if $v;
478         $editor->commit;
479     # tell the client the merge is complete, then merge the holds
480     $conn->respond_complete(1);
481     merge_holds($master, $records);
482         return undef;
483 }
484
485 sub merge_holds {
486     my($master, $records) = @_;
487     return unless $master and @$records;
488     return if @$records == 1 and $master == $$records[0];
489
490     my $e = new_editor(xact=>1);
491     my $holds = $e->search_action_hold_request(
492         {   cancel_time => undef, 
493             fulfillment_time => undef,
494             hold_type => 'T',
495             target => $records
496         },
497         {idlist=>1}
498     );
499
500     for my $hold_id (@$holds) {
501
502         my $hold = $e->retrieve_action_hold_request($hold_id);
503
504         $logger->info("Changing hold ".$hold->id.
505             " target from ".$hold->target." to $master in record merge");
506
507         $hold->target($master);
508         unless($e->update_action_hold_request($hold)) {
509             my $evt = $e->event;
510             $logger->error("Error updating hold ". $evt->textcode .":". $evt->desc .":". $evt->stacktrace); 
511         }
512     }
513
514     $e->commit;
515     return undef;
516 }
517
518
519 __PACKAGE__->register_method(
520         method  => "fleshed_volume_update",
521         api_name        => "open-ils.cat.asset.volume.fleshed.batch.update",);
522
523 __PACKAGE__->register_method(
524         method  => "fleshed_volume_update",
525         api_name        => "open-ils.cat.asset.volume.fleshed.batch.update.override",);
526
527 sub fleshed_volume_update {
528         my( $self, $conn, $auth, $volumes, $delete_stats ) = @_;
529         my( $reqr, $evt ) = $U->checkses($auth);
530         return $evt if $evt;
531
532         my $override = ($self->api_name =~ /override/);
533         my $editor = new_editor( requestor => $reqr, xact => 1 );
534
535         for my $vol (@$volumes) {
536                 $logger->info("vol-update: investigating volume ".$vol->id);
537
538                 $vol->editor($reqr->id);
539                 $vol->edit_date('now');
540
541                 my $copies = $vol->copies;
542                 $vol->clear_copies;
543
544                 $vol->editor($editor->requestor->id);
545                 $vol->edit_date('now');
546
547                 if( $vol->isdeleted ) {
548
549                         $logger->info("vol-update: deleting volume");
550                         my $cs = $editor->search_asset_copy(
551                                 { call_number => $vol->id, deleted => 'f' } );
552                         return OpenILS::Event->new(
553                                 'VOLUME_NOT_EMPTY', payload => $vol->id ) if @$cs;
554
555                         $vol->deleted('t');
556                         return $editor->event unless
557                                 $editor->update_asset_call_number($vol);
558
559                         
560                 } elsif( $vol->isnew ) {
561                         $logger->info("vol-update: creating volume");
562                         $evt = OpenILS::Application::Cat::AssetCommon->create_volume( $override, $editor, $vol );
563                         return $evt if $evt;
564
565                 } elsif( $vol->ischanged ) {
566                         $logger->info("vol-update: update volume");
567                         $evt = update_volume($vol, $editor);
568                         return $evt if $evt;
569                 }
570
571                 # now update any attached copies
572                 if( $copies and @$copies and !$vol->isdeleted ) {
573                         $_->call_number($vol->id) for @$copies;
574                         $evt = OpenILS::Application::Cat::AssetCommon->update_fleshed_copies(
575                 $editor, $override, $vol, $copies, $delete_stats);
576                         return $evt if $evt;
577                 }
578         }
579
580         $editor->finish;
581         return scalar(@$volumes);
582 }
583
584
585 sub update_volume {
586         my $vol = shift;
587         my $editor = shift;
588         my $evt;
589
590         return $evt if ( $evt = OpenILS::Application::Cat::AssetCommon->org_cannot_have_vols($editor, $vol->owning_lib) );
591
592         my $vols = $editor->search_asset_call_number( { 
593                         owning_lib      => $vol->owning_lib,
594                         record          => $vol->record,
595                         label                   => $vol->label,
596                         deleted         => 'f'
597                 }
598         );
599
600         # There exists a different volume in the DB with the same properties
601         return OpenILS::Event->new('VOLUME_LABEL_EXISTS', payload => $vol->id)
602                 if grep { $_->id ne $vol->id } @$vols;
603
604         return $editor->event unless $editor->update_asset_call_number($vol);
605         return undef;
606 }
607
608
609
610 __PACKAGE__->register_method (
611         method => 'delete_bib_record',
612         api_name => 'open-ils.cat.biblio.record_entry.delete');
613
614 sub delete_bib_record {
615     my($self, $conn, $auth, $rec_id) = @_;
616     my $e = new_editor(xact=>1, authtoken=>$auth);
617     return $e->die_event unless $e->checkauth;
618     return $e->die_event unless $e->allowed('DELETE_RECORD', $e->requestor->ws_ou);
619     my $vols = $e->search_asset_call_number({record=>$rec_id, deleted=>'f'});
620     return OpenILS::Event->new('RECORD_NOT_EMPTY', payload=>$rec_id) if @$vols;
621     my $evt = OpenILS::Application::Cat::BibCommon->delete_rec($e, $rec_id);
622     if($evt) { $e->rollback; return $evt; }   
623     $e->commit;
624     return 1;
625 }
626
627
628
629 __PACKAGE__->register_method (
630         method => 'batch_volume_transfer',
631         api_name => 'open-ils.cat.asset.volume.batch.transfer',
632 );
633
634 __PACKAGE__->register_method (
635         method => 'batch_volume_transfer',
636         api_name => 'open-ils.cat.asset.volume.batch.transfer.override',
637 );
638
639
640 sub batch_volume_transfer {
641         my( $self, $conn, $auth, $args ) = @_;
642
643         my $evt;
644         my $rec         = $$args{docid};
645         my $o_lib       = $$args{lib};
646         my $vol_ids = $$args{volumes};
647
648         my $override = 1 if $self->api_name =~ /override/;
649
650         $logger->info("merge: transferring volumes to lib=$o_lib and record=$rec");
651
652         my $e = new_editor(authtoken => $auth, xact =>1);
653         return $e->event unless $e->checkauth;
654         return $e->event unless $e->allowed('UPDATE_VOLUME', $o_lib);
655
656         my $dorg = $e->retrieve_actor_org_unit($o_lib)
657                 or return $e->event;
658
659         my $ou_type = $e->retrieve_actor_org_unit_type($dorg->ou_type)
660                 or return $e->event;
661
662         return $evt if ( $evt = OpenILS::Application::Cat::AssetCommon->org_cannot_have_vols($e, $o_lib) );
663
664         my $vols = $e->batch_retrieve_asset_call_number($vol_ids);
665         my @seen;
666
667    my @rec_ids;
668
669         for my $vol (@$vols) {
670
671         # if we've already looked at this volume, go to the next
672         next if !$vol or grep { $vol->id == $_ } @seen;
673
674                 # grab all of the volumes in the list that have 
675                 # the same label so they can be merged
676                 my @all = grep { $_->label eq $vol->label } @$vols;
677
678                 # take note of the fact that we've looked at this set of volumes
679                 push( @seen, $_->id ) for @all;
680         push( @rec_ids, $_->record ) for @all;
681
682                 # for each volume, see if there are any copies that have a 
683                 # remote circ_lib (circ_lib != vol->owning_lib and != $o_lib ).  
684                 # if so, warn them
685                 unless( $override ) {
686                         for my $v (@all) {
687
688                                 $logger->debug("merge: searching for copies with remote circ_lib for volume ".$v->id);
689                                 my $args = { 
690                                         call_number     => $v->id, 
691                                         circ_lib                => { "not in" => [ $o_lib, $v->owning_lib ] },
692                                         deleted         => 'f'
693                                 };
694
695                                 my $copies = $e->search_asset_copy($args, {idlist=>1});
696
697                                 # if the copy's circ_lib matches the destination lib,
698                                 # that's ok too
699                                 return OpenILS::Event->new('COPY_REMOTE_CIRC_LIB') if @$copies;
700                         }
701                 }
702
703                 # see if there is a volume at the destination lib that 
704                 # already has the requested label
705                 my $existing_vol = $e->search_asset_call_number(
706                         {
707                                 label                   => $vol->label, 
708                                 record          =>$rec, 
709                                 owning_lib      =>$o_lib,
710                                 deleted         => 'f'
711                         }
712                 )->[0];
713
714                 if( $existing_vol ) {
715
716                         if( grep { $_->id == $existing_vol->id } @all ) {
717                                 # this volume is already accounted for in our list of volumes to merge
718                                 $existing_vol = undef;
719
720                         } else {
721                                 # this volume exists on the destination record/owning_lib and must
722                                 # be used as the destination for merging
723                                 $logger->debug("merge: volume already exists at destination record: ".
724                                         $existing_vol->id.' : '.$existing_vol->label) if $existing_vol;
725                         }
726                 } 
727
728                 if( @all > 1 || $existing_vol ) {
729                         $logger->info("merge: found collisions in volume transfer");
730                         my @args = ($e, \@all);
731                         @args = ($e, \@all, $existing_vol) if $existing_vol;
732                         ($vol, $evt) = OpenILS::Application::Cat::Merge::merge_volumes(@args);
733                         return $evt if $evt;
734                 } 
735                 
736                 if( !$existing_vol ) {
737
738                         $vol->owning_lib($o_lib);
739                         $vol->record($rec);
740                         $vol->editor($e->requestor->id);
741                         $vol->edit_date('now');
742         
743                         $logger->info("merge: updating volume ".$vol->id);
744                         $e->update_asset_call_number($vol) or return $e->event;
745
746                 } else {
747                         $logger->info("merge: bypassing volume update because existing volume used as target");
748                 }
749
750                 # regardless of what volume was used as the destination, 
751                 # update any copies that have moved over to the new lib
752                 my $copies = $e->search_asset_copy({call_number=>$vol->id, deleted => 'f'});
753
754                 # update circ lib on the copies - make this a method flag?
755                 for my $copy (@$copies) {
756                         next if $copy->circ_lib == $o_lib;
757                         $logger->info("merge: transfer moving circ lib on copy ".$copy->id);
758                         $copy->circ_lib($o_lib);
759                         $copy->editor($e->requestor->id);
760                         $copy->edit_date('now');
761                         $e->update_asset_copy($copy) or return $e->event;
762                 }
763
764                 # Now see if any empty records need to be deleted after all of this
765
766         for(@rec_ids) {
767             $logger->debug("merge: seeing if we should delete record $_...");
768             $evt = OpenILS::Application::Cat::BibCommon->delete_rec($e, $_) 
769                 if OpenILS::Application::Cat::BibCommon->title_is_empty($e, $_);
770             return $evt if $evt;
771         }
772         }
773
774         $logger->info("merge: transfer succeeded");
775         $e->commit;
776         return 1;
777 }
778
779
780
781
782 __PACKAGE__->register_method(
783         api_name => 'open-ils.cat.call_number.find_or_create',
784         method => 'find_or_create_volume',
785 );
786
787 sub find_or_create_volume {
788         my( $self, $conn, $auth, $label, $record_id, $org_id ) = @_;
789         my $e = new_editor(authtoken=>$auth, xact=>1);
790         return $e->die_event unless $e->checkauth;
791     my ($vol, $evt, $exists) = 
792         OpenILS::Application::Cat::AssetCommon->find_or_create_volume($e, $label, $record_id, $org_id);
793     return $evt if $evt;
794     $e->rollback if $exists;
795     $e->commit if $vol;
796     return $vol->id;
797 }
798
799
800
801 1;