]> git.evergreen-ils.org Git - Evergreen.git/blob - Open-ILS/src/perlmods/OpenILS/Application/Cat/Merge.pm
added support for volume-level holds migration during volume merging
[Evergreen.git] / Open-ILS / src / perlmods / OpenILS / Application / Cat / Merge.pm
1 use strict; use warnings;
2 package OpenILS::Application::Cat::Merge;
3 use base qw/OpenSRF::Application/;
4 use OpenSRF::Application;
5 use OpenILS::Application::AppUtils;
6 use OpenILS::Application::Cat::Utils;
7 use OpenSRF::EX qw(:try);
8 use OpenILS::Utils::Fieldmapper;
9 use OpenILS::Event;
10 use OpenSRF::Utils::Logger qw($logger);
11 use Data::Dumper;
12 my $U = "OpenILS::Application::AppUtils";
13
14 my $storage;
15
16
17 # removes items from an array and returns the removed items
18 # example : my @d = rgrep(sub { $_ =~ /o/ }, \@a);
19 # there's surely a smarter way to do this
20 sub rgrep {
21    my( $sub, $arr ) = @_;
22    my @del;
23    for( my $i = 0; $i < @$arr; $i++ ) {
24       my $a = $$arr[$i];
25       local $_ = $a;
26       if($sub->()) {
27          splice(@$arr, $i--, 1);
28          push( @del, $a );
29       }
30    }
31    return @del;
32 }
33
34
35
36 # takes a master record and a list of 
37 # sub-records to merge into the master record
38 sub merge_records {
39         my( $editor, $master, $records ) = @_;
40
41         my $vol;
42         my $evt;
43
44         my %r = map { $_ => 1 } ($master, @$records); # unique the ids
45         my @recs = keys %r;
46
47         my $reqr = $editor->requestor;
48         $logger->activity("merge: user ".$reqr->id." merging bib records: @recs with master = $master");
49
50         # -----------------------------------------------------------
51         # collect all of the volumes, merge any with duplicate 
52         # labels, then move all of the volumes to the master record
53         # -----------------------------------------------------------
54         my @volumes;
55         for (@recs) {
56                 my $vs = $editor->search_asset_call_number({record => $_, deleted=>'f'});
57                 push( @volumes, @$vs );
58         }
59
60         $logger->info("merge: merge recovered ".scalar(@volumes)." total volumes");
61
62         my @trimmed;
63         # de-duplicate any volumes with the same label and owning_lib
64
65         my %seen_vols;
66
67         for my $v (@volumes) {
68                 my $l = $v->label;
69                 my $o = $v->owning_lib;
70
71                 if($seen_vols{$v->id}) {
72                         $logger->debug("merge: skipping ".$v->id." since it's already been merged");
73                         next;
74                 }
75
76                 $seen_vols{$v->id} = 1;
77
78                 $logger->debug("merge: [".$v->id."] looking for dupes with label $l and owning_lib $o");
79
80                 my @dups;
81                 for my $vv (@volumes) {
82                         if( $vv->label eq $v->label and $vv->owning_lib == $v->owning_lib ) {
83                                 $logger->debug("merge: pushing dupe volume ".$vv->id) if @dups;
84                                 push( @dups, $vv );
85                                 $seen_vols{$vv->id} = 1;
86                         } 
87                 }
88
89                 if( @dups == 1 ) {
90                         $logger->debug("merge: pushing unique volume into trimmed volume set: ".$v->id);
91                         push( @trimmed, @dups );
92
93                 } else {
94                         my($vol, $e) = merge_volumes($editor, \@dups);
95                         return $e if $e;
96                         $logger->debug("merge: pushing vol-merged volume into trimmed volume set: ".$vol->id);
97                         push(@trimmed, $vol);
98                 }
99         }
100
101         my $s = 'merge: trimmed volume set contains the following vols: ';
102         $s .= 'id = '.$_->id .' : record = '.$_->record.' | ' for @trimmed;
103         $logger->debug($s);
104
105         # make all the volumes point to the master record
106         my $stat;
107         for $vol (@trimmed) {
108                 if( $vol->record ne $master ) {
109
110                         $logger->debug("merge: moving volume ".
111                                 $vol->id." from record ".$vol->record. " to $master");
112
113                         $vol->editor( $editor->requestor->id );
114                         $vol->edit_date('now');
115                         $vol->record( $master );
116                         $editor->update_asset_call_number($vol)
117                                 or return $editor->die_event;
118                 }
119         }
120
121         # cycle through and delete the non-master records
122         for my $rec (@recs) {
123
124                 my $record = $editor->retrieve_biblio_record_entry($rec)
125             or return $editor->die_event;
126
127                 $logger->debug("merge: seeing if record $rec needs to be deleted or un-deleted");
128
129                 if( $rec == $master ) {
130                         # make sure the master record is not deleted
131                         if( $U->is_true($record->deleted) ) {
132                                 $logger->info("merge: master record is marked as deleted...un-deleting.");
133                                 $record->deleted('f');
134                                 $record->editor($reqr->id);
135                                 $record->edit_date('now');
136                                 $editor->update_biblio_record_entry($record, {checkperm => 1})
137                                         or return $editor->die_event;
138                         }
139
140                 } else {
141                         $logger->info("merge: deleting record $rec");
142                         $record->deleted('t');
143                         $record->editor($reqr->id);
144                         $record->edit_date('now');
145                         $editor->update_biblio_record_entry($record, {checkperm => 1})
146                                 or return $editor->die_event;
147                 }
148         }
149
150         return undef;
151 }
152
153
154
155 # takes a list of volume objects, picks the volume with most
156 # copies and moves all copies attached to the other volumes
157 # into said volume.  all other volumes are deleted
158 sub merge_volumes {
159         my( $editor, $volumes, $master ) = @_;
160         my %copies;
161         my $evt;
162
163         return ($$volumes[0]) if !$master and @$volumes == 1;
164
165         return ($$volumes[0]) if 
166                 $master and @$volumes == 1 
167                 and $master->id == $$volumes[0]->id;
168
169         $logger->debug("merge: fetching copies for volume list of size ".scalar(@$volumes));
170
171         # collect all of the copies attached to the selected volumes
172         for( @$volumes ) {
173                 $copies{$_->id} = $editor->search_asset_copy({call_number=>$_->id, deleted=>'f'});
174                 $logger->debug("merge: found ".scalar(@{$copies{$_->id}})." copies for volume ".$_->id);
175         }
176         
177         my $bigcn;
178         if( $master ) {
179
180                 # the caller has chosen the master record
181                 $bigcn = $master->id;
182                 push( @$volumes, $master );
183
184         } else {
185
186                 # find the CN with the most copies and make it the master CN
187                 my $big = 0;
188                 for my $cn (keys %copies) {
189                         my $count = scalar(@{$copies{$cn}});
190                         if( $count > $big ) {
191                                 $big = $count;
192                                 $bigcn = $cn;
193                         }
194                 }
195         }
196
197         $bigcn = $$volumes[0]->id unless $bigcn;
198
199         $logger->info("merge: merge using volume $bigcn as the master");
200
201         # now move all of the copies to the new volume
202         for my $cn (keys %copies) {
203                 next if $cn == $bigcn;
204                 for my $copy (@{$copies{$cn}}) {
205                         $logger->debug("merge: setting call_number to $bigcn for copy ".$copy->id);
206                         $copy->call_number($bigcn);
207                         $copy->editor($editor->requestor->id);
208                         $copy->edit_date('now');
209                         $editor->update_asset_copy($copy) or return (undef, $editor->die_event);
210                 }
211         }
212
213         for( @$volumes ) {
214                 next if $_->id == $bigcn;
215                 $logger->debug("merge: marking call_number as deleted: ".$_->id);
216                 $_->deleted('t');
217                 $_->editor($editor->requestor->id);
218                 $_->edit_date('now');
219                 return (undef,$editor->die_event) unless $editor->allowed('UPDATE_VOLUME', $_->owning_lib);
220                 $editor->update_asset_call_number($_) or return (undef, $editor->die_event);
221         merge_volume_holds($editor, $bigcn, $_->id);
222         }
223
224         my ($mvol) = grep { $_->id == $bigcn } @$volumes;
225         $logger->debug("merge: returning master volume ".$mvol->id);
226         return ($mvol);
227 }
228
229 sub merge_volume_holds {
230     my($e, $master_id, $vol_id) = @_;
231
232     my $holds = $e->search_action_hold_request(
233         {   cancel_time => undef, 
234             fulfillment_time => undef,
235             hold_type => 'V',
236             target => $vol_id
237         }
238     );
239
240     for my $hold (@$holds) {
241
242         $logger->info("Changing hold ".$hold->id.
243             " target from ".$hold->target." to $master_id in volume merge");
244
245         $hold->target($master_id);
246         unless($e->update_action_hold_request($hold)) {
247             my $evt = $e->event;
248             $logger->error("Error updating hold ". $evt->textcode .":". $evt->desc .":". $evt->stacktrace); 
249         }
250     }
251
252     return undef;
253 }
254
255
256 1;
257
258