From 4f8e370b88530d7098ad453d599390132b929713 Mon Sep 17 00:00:00 2001 From: Bill Erickson Date: Tue, 4 Oct 2016 15:23:22 -0400 Subject: [PATCH] LP#1619703 Transfer ACQ lineitem to bib API New open-ils.acq API call for transfering a lineitem and its assets to a new bib record. Any asset.call_number's created from the lineitem are migrated to the new target bib record. Any monograph parts linked to copies created from the lineitem are duplicated (if need) on the target bib record and part copy maps are modified to use the new parts. API: open-ils.acq.lineitem.transfer_to_bib(auth, li_id, target_bib_id) Signed-off-by: Bill Erickson --- .../perlmods/lib/OpenILS/Application/Acq/Order.pm | 223 +++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm index 3eae8602e3..14639b3fdf 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Acq/Order.pm @@ -4180,6 +4180,229 @@ sub li_existing_copies { return $counts->[0]->{id}; } +__PACKAGE__->register_method( + method => 'transfer_lineitem', + api_name => 'open-ils.acq.lineitem.transfer_to_bib', + signature => { + desc => q/Transfers a lineitem and all associated + assets to a different target bib record /, + params => [ + {desc => 'Authentication token', type => 'string'}, + {desc => 'The lineitem id', type => 'number'}, + {desc => 'The target bib record id', type => 'number'}, + ], + return => {desc => q/Updated lineitem on success, event on error/} + } +); + +sub transfer_lineitem { + my($self, $conn, $auth, $li_id, $bib_id, $ops) = @_; + $ops ||= {}; # future use + + my $e = new_editor(authtoken=>$auth, xact=>1); + return $e->die_event unless $e->checkauth; + + my ($li, $evt, $perm_org) = fetch_and_check_li($e, $li_id, 'write'); + return $evt if $evt; + + my $orig_bib_id = $li->eg_bib_id; # capture for later. + + if ($orig_bib_id eq $bib_id) { + # Transferring to the same bib. Nothing to do. + $e->rollback; + return $li; + } + + # Sanity check the target bib. + + my $bre = $e->retrieve_biblio_record_entry($bib_id) + or return $e->die_event; + + if ($U->is_true($bre->deleted)) { + $e->rollback; + return OpenILS::Event->new( + 'BAD_PARAMS', {note => 'Target bib is deleted'}); + } + + # Point the lineitem at the selected bib record. Note this will + # work even if the lineitem is not currently pointing at any bib + # record. IOW, this could be used to manually link LI's to bibs. + + $li->eg_bib_id($bib_id); + $li->edit_time('now'); + $li->editor($e->requestor->id); + $e->update_acq_lineitem($li) or return $e->die_event; + + + # Transfer any asset.call_number's (with their linked asset.copy's) + # that were created for this lineitem to the new bib record. + my $acp_ids = $e->json_query({ + select => {acqlid => ['eg_copy_id']}, + from => 'acqlid', + where => {lineitem => $li_id} + }); + + # TODO: Transfer monograph parts. + + my $copies = $e->search_asset_copy( + {id => [map {$_->{eg_copy_id}} @$acp_ids]}); + + if (@$copies) { + + # Group copies into call number batches so each call number can + # be assessed and processed once. + my %cn_batches; + for my $copy (@$copies) { + my $cn_id = $copy->call_number; + $cn_batches{$cn_id} = [] unless $cn_batches{$cn_id}; + push(@{$cn_batches{$cn_id}}, $copy); + } + + while (my ($cn_id, $cn_copies) = each %cn_batches) { + my $evt = transfer_order_volume($e, $bib_id, $cn_id, $cn_copies); + return $evt if $evt; + } + + # Transfer parts as needed to the target bib record. + my $part_maps = $e->search_asset_copy_part_map([ + {target_copy => [map {$_->id} @$copies]}, + {flesh => 1, flesh_fields => {acpm => ['part']}} + ]); + + for my $map (@$part_maps) { + my $evt = transfer_order_copy_parts($e, $bib_id, $map); + return $evt if $evt; + } + } + + $e->commit; + + return $li; +} + +# Transfer copy part map from the source bib to the target bib +# If a part exists on the target bib with the same label, use it. +# Otherwise, create a new part linked to the target bib to be used +# by the migrated copy. +# +# Returns undef on success, event on error. +sub transfer_order_copy_parts { + my ($e, $bib_id, $map) = @_; + + # See if the same part exists on the target bib. + my $existing = $e->search_biblio_monograph_part({ + record => $bib_id, + label => $map->part->label, + deleted => 'f' + })->[0]; + + if ($existing) { + # matching part exists on target record. + # Teach existing copy part map to use it. + + $map->part($existing->id); + + } else { + # No matching part exists on the target record. + # Create one and teach the existing copy part map to use it. + + my $new_part = Fieldmapper::biblio::monograph_part->new; + $new_part->record($bib_id); + $new_part->label($map->part->label); + $new_part->label_sortkey($map->part->label_sortkey); + $e->create_biblio_monograph_part($new_part) or return $e->die_event; + + $map->part($new_part->id); + } + + $e->update_asset_copy_part_map($map) or return $e->die_event; + + return undef; +} + +# 1. If every copy linked to the CN is represented by ordered copies +# -- the ones we're processing here -- then transfer the call number +# wholesale to the new bib record. +# +# 2. Otherwise, find-or-create a like call number for the target +# bib record and update the ordered copies to use the new/found +# call number. +# +# Returns undef on success, event on error. +sub transfer_order_volume { + my ($e, $bib_id, $cn_id, $cn_copies) = @_; + + my $cn = $e->retrieve_asset_call_number($cn_id) or return $e->die_event; + + my $copy_count = $e->json_query({ + select => {acp => [{ + aggregate => 1, + transform => 'count', + column => 'id' + }]}, + from => 'acp', + where => {call_number => $cn_id} + })->[0]; + + my $target_cn; + my $evt; + + if ($copy_count->{count} == scalar(@$cn_copies)) { + # Order copies represent all copies linked to the callnumber + # See if a matching CN exists at the target bib and, if so, + # transfer our copies to the existing CN. Otherwise, + # simply point our call number at the new bib. + + # See if a matching CN already exists at the target bib + $target_cn = OpenILS::Application::Cat::AssetCommon->volume_exists( + $e, $bib_id, $cn->label, $cn->owning_lib, $cn->prefix, $cn->suffix + ); + + if ($target_cn) { + # We are transferring all attached copies to the matching + # callnumber on the target bib. This call number is no + # longer needed. Delete it. + $evt = OpenILS::Application::Cat::AssetCommon->delete_volume( + $e, $cn, + 1, # override + 0, # delete copies + 1 # skip copy checks + ); + + return $evt if $evt; + + } else { + # No matching CN exists. Point our CN at the target bib. + $cn->record($bib_id); + $cn->edit_date('now'); + $cn->editor($e->requestor->id); + $e->update_asset_call_number($cn) or return $e->die_event; + return undef; # all done + } + } + + # Copies need to be migrated to the target call number. + + ($target_cn, $evt) = + OpenILS::Application::Cat::AssetCommon->find_or_create_volume( + $e, $cn->label, $bib_id, $cn->owning_lib, + $cn->prefix, $cn->suffix, $cn->label_class + ) unless $target_cn; + + return $evt if $evt; + + # ... transfer copies. + # Transfer order copies to the new call number. + for my $copy (@$cn_copies) { + $copy->call_number($target_cn->id); + $copy->edit_date('now'); + $copy->editor($e->requestor->id); + $e->update_asset_copy($copy) or return $e->die_event; + } + + return undef; +} + 1; -- 2.11.0