$template = $e->retrieve_biblio_record_entry( $titem->target_biblio_record_entry )->marc;
}
- my $responses = [];
- my $some_failed = 0;
+ my $num_failed = 0;
+ my $num_succeeded = 0;
$conn->respond_complete(
- $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses)->gather(1)
+ $actor->request('open-ils.actor.anon_cache.set_value', $auth, batch_edit_progress => {})->gather(1)
) if ($actor);
for my $item ( @$items ) {
)->[0]->{'vandelay.template_overlay_bib_record'};
}
- $some_failed++ if ($success eq 'f');
+ if ($success eq 'f') {
+ $num_failed++;
+ } else {
+ $num_succeeded++;
+ }
if ($actor) {
- push @$responses, { record => $rec->id, success => $success };
- $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+ $actor->request(
+ 'open-ils.actor.anon_cache.set_value', $auth,
+ batch_edit_progress => {
+ succeeded => $num_succeeded,
+ failed => $num_failed
+ },
+ );
} else {
$conn->respond({ record => $rec->id, success => $success });
}
unless ($e->delete_container_biblio_record_entry_bucket_item($item)) {
$e->rollback;
if ($actor) {
- push @$responses, { complete => 1, success => 'f' };
- $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+ $actor->request(
+ 'open-ils.actor.anon_cache.set_value', $auth,
+ batch_edit_progress => {
+ complete => 1,
+ success => 'f',
+ succeeded => $num_succeeded,
+ failed => $num_failed,
+ }
+ );
return undef;
} else {
return { complete => 1, success => 'f' };
}
}
- if ($titem && !$some_failed) {
+ if ($titem && !$num_failed) {
return $e->die_event unless ($e->delete_container_biblio_record_entry_bucket_item($titem));
}
if ($e->commit) {
if ($actor) {
- push @$responses, { complete => 1, success => 't' };
- $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+ $actor->request(
+ 'open-ils.actor.anon_cache.set_value', $auth,
+ batch_edit_progress => {
+ complete => 1,
+ success => 't',
+ succeeded => $num_succeeded,
+ failed => $num_failed,
+ }
+ );
} else {
return { complete => 1, success => 't' };
}
} else {
if ($actor) {
- push @$responses, { complete => 1, success => 'f' };
- $actor->request('open-ils.actor.anon_cache.set_value', $auth, res_list => $responses);
+ $actor->request(
+ 'open-ils.actor.anon_cache.set_value', $auth,
+ batch_edit_progress => {
+ complete => 1,
+ success => 'f',
+ succeeded => $num_succeeded,
+ failed => $num_failed,
+ }
+ );
} else {
return { complete => 1, success => 'f' };
}
my $tag = substr($field, 0, 3);
$logger->debug("Tag = $tag");
my @node = $doc->findnodes("//marc:datafield[\@tag='$tag']");
+ next unless (@node);
# Now parse the subfields and build up the subfield XPath
my @subfields = split(//, substr($field, 3));
if (!@subfields) {
@subfields = ('a');
}
- my $subxpath;
- foreach my $sf (@subfields) {
- $subxpath .= "\@code='$sf' or ";
- }
- $subxpath = substr($subxpath, 0, -4);
- $logger->debug("subxpath = $subxpath");
+ my $xpath = 'marc:subfield[' . join(' or ', map { "\@code='$_'" } @subfields) . ']';
+ $logger->debug("xpath = $xpath");
# Find the contents of the specified subfields
foreach my $x (@node) {
- my $cn = $x->findvalue("marc:subfield[$subxpath]");
+ # We can't use find($xpath)->to_literal_delimited here because older 2.x
+ # versions of the XML::LibXML module don't have to_literal_delimited().
+ my $cn = join(
+ ' ',
+ map { $_->textContent } $x->findnodes($xpath)
+ );
push @res, {$tag => $cn} if ($cn);
}
}
@org_ids = ($user_obj->home_ou);
}
+ # Create an editor that can be shared across all iterations of
+ # _build_volume_list(). Otherwise, .authoritative calls can result
+ # in creating too many cstore connections.
+ my $e = new_editor();
+
if( $self->api_name =~ /global/ ) {
- return _build_volume_list( { record => $docid, deleted => 'f', label => { '<>' => '##URI##' } } );
+ return _build_volume_list($e, { record => $docid, deleted => 'f', label => { '<>' => '##URI##' } } );
} else {
my @all_vols;
for my $orgid (@org_ids) {
- my $vols = _build_volume_list(
+ my $vols = _build_volume_list($e,
{ record => $docid, owning_lib => $orgid, deleted => 'f', label => { '<>' => '##URI##' } } );
push( @all_vols, @$vols );
}
sub _build_volume_list {
+ my $e = shift;
my $search_hash = shift;
+ $e ||= new_editor();
+
$search_hash->{deleted} = 'f';
- my $e = new_editor();
my $vols = $e->search_asset_call_number([
$search_hash,
my $copies = $e->search_asset_copy([
{ call_number => $volume->id , deleted => 'f' },
- { flesh => 1, flesh_fields => { acp => ['stat_cat_entries','parts'] } }
+ {
+ join => {
+ acpm => {
+ type => 'left',
+ join => {
+ bmp => { type => 'left' }
+ }
+ }
+ },
+ flesh => 1,
+ flesh_fields => { acp => ['stat_cat_entries','parts'] },
+ order_by => [
+ {'class' => 'bmp', 'field' => 'label_sortkey', 'transform' => 'oils_text_as_bytea'},
+ {'class' => 'bmp', 'field' => 'label', 'transform' => 'oils_text_as_bytea'},
+ {'class' => 'acp', 'field' => 'barcode'}
+ ]
+ }
]);
- $copies = [ sort { $a->barcode cmp $b->barcode } @$copies ];
-
for my $c (@$copies) {
if( $c->status == OILS_COPY_STATUS_CHECKED_OUT ) {
$c->circulations(
# flesh and munge the copies
my $fleshed_copies = [];
- my ($copy, $copy_evt);
+ my $copy;
foreach my $copy_id ( @{ $copies } ) {
- ($copy, $copy_evt) = $U->fetch_copy($copy_id);
- return $copy_evt if $copy_evt;
+ $copy = $editor->search_asset_copy([
+ { id => $copy_id , deleted => 'f' },
+ {
+ flesh => 1,
+ flesh_fields => { acp => ['parts', 'stat_cat_entries'] }
+ }
+ ])->[0];
+ return OpenILS::Event->new('ASSET_COPY_NOT_FOUND') if !$copy;
$copy->call_number( $volume );
$copy->circ_lib( $cn->owning_lib() );
$copy->ischanged( 't' );
return $evt;
}
+ # take care of the parts
+ for my $copy (@$fleshed_copies) {
+ my $parts = $copy->parts;
+ next unless $parts;
+ my $part_objs = [];
+ foreach my $part (@$parts) {
+ my $part_label = $part->label;
+ my $part_obj = $editor->search_biblio_monograph_part(
+ {
+ label=>$part_label,
+ record=>$cn->record,
+ deleted=>'f'
+ }
+ )->[0];
+ if (!$part_obj) {
+ $part_obj = Fieldmapper::biblio::monograph_part->new();
+ $part_obj->label( $part_label );
+ $part_obj->record( $cn->record );
+ unless($editor->create_biblio_monograph_part($part_obj)) {
+ return $editor->die_event if $editor->die_event;
+ }
+ }
+ push @$part_objs, $part_obj;
+ }
+ $copy->parts( $part_objs );
+ $copy->ischanged(1);
+ $evt = OpenILS::Application::Cat::AssetCommon->update_copy_parts($editor, $copy, 1); #delete_parts=1
+ return $evt if $evt;
+ }
+
$editor->commit;
$logger->info("copy to volume transfer successfully updated ".scalar(@$copies)." copies");
reset_hold_list($auth, $retarget_holds);
}
__PACKAGE__->register_method(
+ method => 'calculate_marc_merge',
+ api_name => 'open-ils.cat.merge.marc.per_profile',
+ signature => q/
+ Calculate the result of merging one or more MARC records
+ per the specified merge profile
+ @param auth The login session key
+ @param merge_profile ID of the record merge profile
+ @param records Array of two or more MARCXML records to be
+ merged. If two are supplied, the first
+ is treated as the record to be overlaid,
+ and the the incoming record that will
+ overlay the first. If more than two are
+ supplied, the first is treated as the
+ record to be overlaid, and each following
+ record in turn will be merged into that
+ record.
+ @return MARCXML string of the results of the merge
+ /
+);
+__PACKAGE__->register_method(
+ method => 'calculate_bib_marc_merge',
+ api_name => 'open-ils.cat.merge.biblio.per_profile',
+ signature => q/
+ Calculate the result of merging one or more bib records
+ per the specified merge profile
+ @param auth The login session key
+ @param merge_profile ID of the record merge profile
+ @param records Array of two or more bib record IDs of
+ the bibs to be merged.
+ @return MARCXML string of the results of the merge
+ /
+);
+__PACKAGE__->register_method(
+ method => 'calculate_authority_marc_merge',
+ api_name => 'open-ils.cat.merge.authority.per_profile',
+ signature => q/
+ Calculate the result of merging one or more authority records
+ per the specified merge profile
+ @param auth The login session key
+ @param merge_profile ID of the record merge profile
+ @param records Array of two or more bib record IDs of
+ the bibs to be merged.
+ @return MARCXML string of the results of the merge
+ /
+);
+
+sub _handle_marc_merge {
+ my ($e, $merge_profile_id, $records) = @_;
+
+ my $result = shift @$records;
+ foreach my $incoming (@$records) {
+ my $response = $e->json_query({
+ from => [
+ 'vandelay.merge_record_xml_using_profile',
+ $incoming, $result,
+ $merge_profile_id
+ ]
+ });
+ return unless ref($response);
+ $result = $response->[0]->{'vandelay.merge_record_xml_using_profile'};
+ }
+ return $result;
+}
+
+sub calculate_marc_merge {
+ my( $self, $conn, $auth, $merge_profile_id, $records ) = @_;
+
+ my $e = new_editor(authtoken=>$auth, xact=>1);
+ return $e->die_event unless $e->checkauth;
+
+ my $merge_profile = $e->retrieve_vandelay_merge_profile($merge_profile_id)
+ or return $e->die_event;
+ return $e->die_event unless ref($records) && @$records >= 2;
+
+ return _handle_marc_merge($e, $merge_profile_id, $records)
+}
+
+sub calculate_bib_marc_merge {
+ my( $self, $conn, $auth, $merge_profile_id, $bib_ids ) = @_;
+
+ my $e = new_editor(authtoken=>$auth, xact=>1);
+ return $e->die_event unless $e->checkauth;
+
+ my $merge_profile = $e->retrieve_vandelay_merge_profile($merge_profile_id)
+ or return $e->die_event;
+ return $e->die_event unless ref($bib_ids) && @$bib_ids >= 2;
+
+ my $records = [];
+ foreach my $id (@$bib_ids) {
+ my $bre = $e->retrieve_biblio_record_entry($id) or return $e->die_event;
+ push @$records, $bre->marc();
+ }
+
+ return _handle_marc_merge($e, $merge_profile_id, $records)
+}
+
+sub calculate_authority_marc_merge {
+ my( $self, $conn, $auth, $merge_profile_id, $authority_ids ) = @_;
+
+ my $e = new_editor(authtoken=>$auth, xact=>1);
+ return $e->die_event unless $e->checkauth;
+
+ my $merge_profile = $e->retrieve_vandelay_merge_profile($merge_profile_id)
+ or return $e->die_event;
+ return $e->die_event unless ref($authority_ids) && @$authority_ids >= 2;
+
+ my $records = [];
+ foreach my $id (@$authority_ids) {
+ my $are = $e->retrieve_authority_record_entry($id) or return $e->die_event;
+ push @$records, $are->marc();
+ }
+
+ return _handle_marc_merge($e, $merge_profile_id, $records)
+}
+
+__PACKAGE__->register_method(
method => "fleshed_volume_update",
api_name => "open-ils.cat.asset.volume.fleshed.batch.update",);
my $retarget_holds = [];
my $auto_merge_vols = $options->{auto_merge_vols};
my $create_parts = $options->{create_parts};
+ my $copy_ids = [];
for my $vol (@$volumes) {
$logger->info("vol-update: investigating volume ".$vol->id);
} elsif( $vol->isnew ) {
$logger->info("vol-update: creating volume");
- $evt = $assetcom->create_volume( $oargs, $editor, $vol );
+ ($vol,$evt) = $assetcom->create_volume( $auto_merge_vols ? { all => 1} : $oargs, $editor, $vol );
return $evt if $evt;
} elsif( $vol->ischanged ) {
$logger->info("vol-update: update volume");
- my $resp = update_volume($vol, $editor, ($oargs->{all} or grep { $_ eq 'VOLUME_LABEL_EXISTS' } @{$oargs->{events}} or $auto_merge_vols));
- return $resp->{evt} if $resp->{evt};
- $vol = $resp->{merge_vol} if $resp->{merge_vol};
+
+ # Three cases here:
+ # 1) We're editing a volume, and not its copies.
+ # 2) We're editing a volume, and a subset of its copies.
+ # 3) We're editing a volume, and all of its copies.
+ #
+ # For 1) and 3), we definitely want to edit the volume
+ # itself (and possibly auto-merge), but for 2), we want
+ # to create a new volume (and possibly auto-merge).
+
+ if (scalar(@$copies) == 0) { # case 1
+
+ my $resp = update_volume($vol, $editor, ($oargs->{all} or grep { $_ eq 'VOLUME_LABEL_EXISTS' } @{$oargs->{events}} or $auto_merge_vols));
+ return $resp->{evt} if $resp->{evt};
+ $vol = $resp->{merge_vol} if $resp->{merge_vol};
+
+ } else {
+
+ my $resp = $editor->json_query({
+ select => {
+ acp => [
+ {transform => 'count', aggregate => 1, column => 'id', alias => 'count'}
+ ]
+ },
+ from => 'acp',
+ where => {
+ call_number => $vol->id,
+ deleted => 'f',
+ id => {'not in' => [ map { $_->id } @$copies ]}
+ }
+ });
+ if ($resp->[0]->{count} && $resp->[0]->{count} > 0) { # case 2
+
+ ($vol,$evt) = $assetcom->create_volume( $auto_merge_vols ? { all => 1} : $oargs, $editor, $vol );
+ return $evt if $evt;
+
+ } else { # case 3
+
+ my $resp = update_volume($vol, $editor, ($oargs->{all} or grep { $_ eq 'VOLUME_LABEL_EXISTS' } @{$oargs->{events}} or $auto_merge_vols));
+ return $resp->{evt} if $resp->{evt};
+ $vol = $resp->{merge_vol} if $resp->{merge_vol};
+ }
+
+ }
}
# now update any attached copies
$evt = $assetcom->update_fleshed_copies(
$editor, $oargs, $vol, $copies, $delete_stats, $retarget_holds, undef, $create_parts);
return $evt if $evt;
+ push( @$copy_ids, $_->id ) for @$copies;
}
}
$editor->finish;
reset_hold_list($auth, $retarget_holds);
- return scalar(@$volumes);
+ if ($options->{return_copy_ids}) {
+ return $copy_ids;
+ } else {
+ return scalar(@$volumes);
+ }
}
}
}
+ # record the difference between the destination bib and the present bib
+ my $same_bib = $vol->record == $rec;
+
# see if there is a volume at the destination lib that
# already has the requested label
my $existing_vol = $e->search_asset_call_number(
# regardless of what volume was used as the destination,
# update any copies that have moved over to the new lib
- my $copies = $e->search_asset_copy({call_number=>$vol->id, deleted => 'f'});
+ my $copies = $e->search_asset_copy([
+ { call_number => $vol->id , deleted => 'f' },
+ {
+ flesh => 1,
+ flesh_fields => { acp => ['parts'] }
+ }
+ ]);
# update circ lib on the copies - make this a method flag?
for my $copy (@$copies) {
$e->update_asset_copy($copy) or return $e->event;
}
+ # update parts if volume is moving bib records
+ if( !$same_bib ) {
+ for my $copy (@$copies) {
+ my $parts = $copy->parts;
+ next unless $parts;
+ my $part_objs = [];
+ foreach my $part (@$parts) {
+ my $part_label = $part->label;
+ my $part_obj = $e->search_biblio_monograph_part(
+ {
+ label=>$part_label,
+ record=>$rec,
+ deleted=>'f'
+ }
+ )->[0];
+
+ if (!$part_obj) {
+ $part_obj = Fieldmapper::biblio::monograph_part->new();
+ $part_obj->label( $part_label );
+ $part_obj->record( $rec );
+ unless($e->create_biblio_monograph_part($part_obj)) {
+ return $e->die_event if $e->die_event;
+ }
+ }
+ push @$part_objs, $part_obj;
+ }
+
+ $copy->parts( $part_objs );
+ $copy->ischanged(1);
+ $evt = OpenILS::Application::Cat::AssetCommon->update_copy_parts($e, $copy, 1); #delete_parts=1
+ return $evt if $evt;
+ }
+ }
+
# Now see if any empty records need to be deleted after all of this
for(@rec_ids) {
my( $self, $conn, $auth, $marc_format, $marc_record_type ) = @_;
my $e = new_editor( authtoken=>$auth, xact=>1 );
return $e->die_event unless $e->checkauth;
- return $e->die_event unless $e->allowed('UPDATE_MARC', $e->requestor->ws_ou);
my $field_list_only = ($self->api_name =~ /\.field_list\./) ? 1 : 0;
my $context_ou;