Merge branch 'master' of git.evergreen-ils.org:Evergreen into social
[evergreen-equinox.git] / Open-ILS / src / perlmods / lib / OpenILS / Application / Serial.pm
index 2ee5cba..832d0c2 100644 (file)
@@ -247,13 +247,26 @@ sub fleshed_item_alter {
     my $editor = new_editor(requestor => $reqr, xact => 1);
     my $override = $self->api_name =~ /override/;
 
-# TODO: permission check
-#        return $editor->event unless
-#            $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
-
+    my %found_sdist_ids;
+    my %found_sstr_ids;
     for my $item (@$items) {
+        my $sstr_id = ref $item->stream ? $item->stream->id : $item->stream;
+        if (!exists($found_sstr_ids{$sstr_id})) {
+            my $sstr;
+            if (ref $item->stream) {
+                $sstr = $item->stream;
+            } else {
+                $sstr = $editor->retrieve_serial_stream($item->stream) or return $editor->die_event;
+            }
+            if (!exists($found_sdist_ids{$sstr->distribution})) {
+                my $sdist = $editor->retrieve_serial_distribution($sstr->distribution) or return $editor->die_event;
+                return $editor->die_event unless
+                    $editor->allowed("ADMIN_SERIAL_STREAM", $sdist->holding_lib);
+                $found_sdist_ids{$sstr->distribution} = 1;
+            }
+            $found_sstr_ids{$sstr_id} = 1;
+        }
 
-        my $itemid = $item->id;
         $item->editor($editor->requestor->id);
         $item->edit_date('now');
 
@@ -370,11 +383,22 @@ sub fleshed_issuance_alter {
     my $editor = new_editor(requestor => $reqr, xact => 1);
     my $override = $self->api_name =~ /override/;
 
-# TODO: permission support
-#        return $editor->event unless
-#            $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
-
+    my %found_ssub_ids;
     for my $issuance (@$issuances) {
+        my $ssub_id = ref $issuance->subscription ? $issuance->subscription->id : $issuance->subscription;
+        if (!exists($found_ssub_ids{$ssub_id})) {
+            my $owning_lib_id;
+            if (ref $issuance->subscription) {
+                $owning_lib_id = $issuance->subscription->owning_lib;
+            } else {
+                my $ssub = $editor->retrieve_serial_subscription($issuance->subscription) or return $editor->die_event;
+                $owning_lib_id = $ssub->owning_lib;
+            }
+            return $editor->die_event unless
+                $editor->allowed("ADMIN_SERIAL_SUBSCRIPTION", $owning_lib_id);
+            $found_ssub_ids{$ssub_id} = 1;
+        }
+
         my $issuanceid = $issuance->id;
         $issuance->editor($editor->requestor->id);
         $issuance->edit_date('now');
@@ -676,11 +700,22 @@ sub fleshed_sunit_alter {
     my $editor = new_editor(requestor => $reqr, xact => 1);
     my $override = $self->api_name =~ /override/;
 
-# TODO: permission support
-#        return $editor->event unless
-#            $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
-
+    my %found_cn_ids;
     for my $sunit (@$sunits) {
+        my $cn_id = ref $sunit->call_number ? $sunit->call_number->id : $sunit->call_number;
+        if (!exists($found_cn_ids{$cn_id})) {
+            my $owning_lib_id;
+            if (ref $sunit->call_number) {
+                $owning_lib_id = $sunit->call_number->owning_lib;
+            } else {
+                my $cn = $editor->retrieve_asset_call_number($sunit->call_number) or return $editor->die_event;
+                $owning_lib_id = $cn->owning_lib;
+            }
+            return $editor->die_event unless
+                $editor->allowed("UPDATE_COPY", $owning_lib_id);
+            $found_cn_ids{$cn_id} = 1;
+        }
+
         if( $sunit->isdeleted ) {
             $evt = _delete_sunit( $editor, $override, $sunit );
         } else {
@@ -903,7 +938,8 @@ sub make_predictions {
         push (@issuances, $issuance);
     }
 
-    fleshed_issuance_alter($self, $conn, $authtoken, \@issuances); # FIXME: catch events
+    my $evt = fleshed_issuance_alter($self, $conn, $authtoken, \@issuances);
+    return $evt if ref $evt;
 
     my @items;
     for (my $i = 0; $i < @issuances; $i++) {
@@ -932,8 +968,6 @@ sub make_predictions {
 # a hash ref of options initially defined as:
 # caption : the caption field to predict on
 # num_to_predict : the number of issues you wish to predict
-# last_rec_date : the date of the last received issue, to be used as an offset
-#                 for predicting future issues
 # faked_chron_date : if the serial does not actually have a chronology caption (but we need one for prediction's sake), base predictions on this date
 #
 # The basic method is to first convert to a single holding if compressed, then
@@ -952,7 +986,6 @@ sub _generate_issuance_values {
     my $end_date = $options->{end_date};
     my $predict_from = $options->{predict_from};   # issuance to predict from
     my $faked_chron_date = $options->{faked_chron_date};   # serial does not have a chronology caption, so add one (temporarily) based on this date 
-    #my $last_rec_date = $options->{last_rec_date};   # expected or actual
 
 
 # Only needed for 'real' MFHD records, not our temp records
@@ -1040,6 +1073,8 @@ sub _revive_holding {
 
     # build MFHD::Holding
     return new MFHD::Holding($seqno, $holding_field, $caption_field);
+
+    # TODO(?) the underlying MARC and the Holding object end up in conflict concerning subfield '8'
 }
 
 __PACKAGE__->register_method(
@@ -1237,11 +1272,13 @@ sub unitize_items {
 
         $found_stream_ids{$stream_id} = 1;
 
-        if (defined($unit_id)) {
+        if (defined($unit_id) and $unit_id ne '') {
             $found_unit_ids{$unit_id} = 1;
             # save the stream_id for this unit_id
             # TODO: prevent items from different streams in same unit? (perhaps in interface)
             $stream_ids_by_unit_id{$unit_id} = $stream_id;
+        } else {
+            $item->clear_unit;
         }
 
         my $evt = _update_sitem($editor, undef, $item);
@@ -1324,6 +1361,7 @@ sub unitize_items {
             } else {
                 $sdist = $editor->search_serial_distribution([{"+sstr" => {"id" => $stream_id}}, { "join" => {"sstr" => {}} }]);
                 $sdist = $sdist->[0];
+                $sdist_by_stream_id{$stream_id} = $sdist;
             }
             my $streams;
             if (!exists($streams_by_sdist{$sdist->id})) {
@@ -1344,7 +1382,7 @@ sub unitize_items {
             foreach my $type (keys %{$found_types{$stream_id}}) {
                 my $issuances = $editor->search_serial_issuance([ {"+sitem" => {"stream" => $stream_id, "status" => "Received"}, "+scap" => {"type" => $type}}, {"join" => {"sitem" => {}, "scap" => {}}, "order_by" => {"siss" => "date_published"}} ]);
                 #TODO: evt on search failure
-                my $evt = _prepare_summaries($editor, $issuances, $sdist_id, $type);
+                my $evt = _prepare_summaries($editor, $issuances, $sdist_by_stream_id{$stream_id}, $type);
                 if ($U->event_code($evt)) {
                     $editor->rollback;
                     return $evt;
@@ -1459,12 +1497,12 @@ sub _prepare_unit {
 # type ('basic', 'index', 'supplement') for a given distribution.
 # It also creates the summary if it doesn't yet exist.
 sub _prepare_summaries {
-    my ($e, $issuances, $dist_id, $type) = @_;
+    my ($e, $issuances, $sdist, $type) = @_;
 
-    my ($mfhd, $formatted_parts) = _summarize_contents($e, $issuances);
+    my ($mfhd, $formatted_parts) = _summarize_contents($e, $issuances, $sdist);
 
     my $search_method = "search_serial_${type}_summary";
-    my $summary = $e->$search_method([{"distribution" => $dist_id}]);
+    my $summary = $e->$search_method([{"distribution" => $sdist->id}]);
 
     my $cu_method = "update";
 
@@ -1473,7 +1511,7 @@ sub _prepare_summaries {
     } else {
         my $class = "Fieldmapper::serial::${type}_summary";
         $summary = $class->new;
-        $summary->distribution($dist_id);
+        $summary->distribution($sdist->id);
         $cu_method = "create";
     }
 
@@ -1660,7 +1698,7 @@ sub receive_items_one_unit_per {
             unless (grep { $_->id == $item->issuance->id } @$issuances_received) {
                 push @$issuances_received, $item->issuance;
             }
-            $evt = _prepare_summaries($e, $issuances_received, $item->stream->distribution->id, $item->issuance->holding_type);
+            $evt = _prepare_summaries($e, $issuances_received, $item->stream->distribution, $item->issuance->holding_type);
             if ($U->event_code($evt)) {
                 $e->rollback;
                 return $evt;
@@ -1751,14 +1789,32 @@ sub _build_unit {
 sub _summarize_contents {
     my $editor = shift;
     my $issuances = shift;
+    my $sdist = shift;
+
+    # create or lookup MFHD record
+    my $mfhd;
+    if ($sdist and defined($sdist->record_entry) and $sdist->summary_method eq 'merge_with_sre') {
+        my $sre;
+        if (ref $sdist->record_entry) {
+            $sre = $sdist->record_entry; 
+        } else {
+            $sre = $editor->retrieve_serial_record_entry($sdist->record_entry);
+        }
+        $mfhd = MFHD->new(MARC::Record->new_from_xml($sre->marc)); 
+    } else {
+        $logger->info($sdist);
+        $mfhd = MFHD->new(MARC::Record->new());
+    }
 
-    # create MFHD record
-    my $mfhd = MFHD->new(MARC::Record->new());
     my %scaps;
     my %scap_fields;
-    my @scap_fields_ordered;
     my $seqno = 1;
-    my $link_id = 1;
+    # We keep track of these separately to avoid link_id contamination,
+    # e.g. a basic issuance, followed by a merging supplement, followed by
+    # another basic.  If we could be sure that they were not mixed, one
+    # value could suffice.
+    my %link_ids = ('basic' => 10000, 'index' => 10000, 'supplement' => 10000);
+    my %first_scap = ('basic' => 1, 'index' => 1, 'supplement' => 1);
     foreach my $issuance (@$issuances) {
         my $scap_id = $issuance->caption_and_pattern;
         next if (!$scap_id); # skip issuances with no caption/pattern
@@ -1770,13 +1826,35 @@ sub _summarize_contents {
             $scaps{$scap_id} = $editor->retrieve_serial_caption_and_pattern($scap_id);
             $scap = $scaps{$scap_id};
             $scap_field = _revive_caption($scap);
+            my $did_merge = 0;
+            if ($first_scap{$scap->type}) { # special merge processing
+                $first_scap{$MFHD_TAGS_BY_NAME{$scap->type}} = 0;
+                if ($sdist and $sdist->summary_method eq 'merge_with_sre') {
+                    # MFHD Caption objects do not yet have a built-in compare (TODO), so let's do a basic one
+                    my @field_85xs = $mfhd->field($MFHD_TAGS_BY_NAME{$scap->type});
+                    if (@field_85xs) {
+                        my $last_caption_field = $field_85xs[-1];
+                        my $last_link_id = $last_caption_field->subfield('8');
+                        # set the link id to match, temporarily, for comparison
+                        $last_caption_field->update('8' => $scap_field->subfield('8'));
+                        my $last_caption_json = OpenSRF::Utils::JSON->perl2JSON([$last_caption_field->indicator(1), $last_caption_field->indicator(2), $last_caption_field->subfields_list]);
+                        if ($last_caption_json eq $scap->pattern_code) { # merge is possible, they match
+                            # restore link id
+                            $link_ids{$scap->type} = $last_link_id;
+                            # set scap_field to last field
+                            $scap_field = $last_caption_field;
+                            $did_merge = 1;
+                        }
+                    }
+                }
+            }
             $scap_fields{$scap_id} = $scap_field;
-            push(@scap_fields_ordered, $scap_field);
-            $scap_field->update('8' => $link_id);
-            $mfhd->append_fields($scap_field);
-            $link_id++;
+            $scap_field->update('8' => $link_ids{$scap->type});
+            # TODO: make MFHD/Caption smarter about this
+            $scap_field->{_mfhdc_LINK_ID} = $link_ids{$scap->type};
+            $mfhd->append_fields($scap_field) if !$did_merge;
+            $link_ids{$scap->type}++;
         } else {
-            $scap = $scaps{$scap_id};
             $scap_field = $scap_fields{$scap_id};
         }
 
@@ -1785,6 +1863,7 @@ sub _summarize_contents {
     }
 
     my @formatted_parts;
+    my @scap_fields_ordered = $mfhd->field('85[345]');
     foreach my $scap_field (@scap_fields_ordered) { #TODO: use generic MFHD "summarize" method, once available
        my @updated_holdings = $mfhd->get_compressed_holdings($scap_field);
        foreach my $holding (@updated_holdings) {
@@ -2023,11 +2102,10 @@ sub fleshed_ssub_alter {
     my $editor = new_editor(requestor => $reqr, xact => 1);
     my $override = $self->api_name =~ /override/;
 
-# TODO: permission check
-#        return $editor->event unless
-#            $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
-
     for my $ssub (@$ssubs) {
+        my $owning_lib_id = ref $ssub->owning_lib ? $ssub->owning_lib->id : $ssub->owning_lib;
+        return $editor->die_event unless
+            $editor->allowed("ADMIN_SERIAL_SUBSCRIPTION", $owning_lib_id);
 
         my $ssubid = $ssub->id;
 
@@ -2250,12 +2328,10 @@ sub fleshed_sdist_alter {
     my $editor = new_editor(requestor => $reqr, xact => 1);
     my $override = $self->api_name =~ /override/;
 
-# TODO: permission check
-#        return $editor->event unless
-#            $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
-
     for my $sdist (@$sdists) {
-        my $sdistid = $sdist->id;
+        my $holding_lib_id = ref $sdist->holding_lib ? $sdist->holding_lib->id : $sdist->holding_lib;
+        return $editor->die_event unless
+            $editor->allowed("ADMIN_SERIAL_DISTRIBUTION", $holding_lib_id);
 
         if( $sdist->isdeleted ) {
             $evt = _delete_sdist( $editor, $override, $sdist);
@@ -2454,12 +2530,14 @@ sub scap_alter {
     my $editor = new_editor(requestor => $reqr, xact => 1);
     my $override = $self->api_name =~ /override/;
 
-# TODO: permission check
-#        return $editor->event unless
-#            $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
-
+    my %found_ssub_ids;
     for my $scap (@$scaps) {
-        my $scapid = $scap->id;
+        if (!exists($found_ssub_ids{$scap->subscription})) {
+            my $ssub = $editor->retrieve_serial_subscription($scap->subscription) or return $editor->die_event;
+            return $editor->die_event unless
+                $editor->allowed("ADMIN_SERIAL_CAPTION_PATTERN", $ssub->owning_lib);
+            $found_ssub_ids{$scap->subscription} = 1;
+        }
 
         if( $scap->isdeleted ) {
             $evt = _delete_scap( $editor, $override, $scap);
@@ -2565,12 +2643,14 @@ sub sstr_alter {
     my $editor = new_editor(requestor => $reqr, xact => 1);
     my $override = $self->api_name =~ /override/;
 
-# TODO: permission check
-#        return $editor->event unless
-#            $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
-
+    my %found_sdist_ids;
     for my $sstr (@$sstrs) {
-        my $sstrid = $sstr->id;
+        if (!exists($found_sdist_ids{$sstr->distribution})) {
+            my $sdist = $editor->retrieve_serial_distribution($sstr->distribution) or return $editor->die_event;
+            return $editor->die_event unless
+                $editor->allowed("ADMIN_SERIAL_STREAM", $sdist->holding_lib);
+            $found_sdist_ids{$sstr->distribution} = 1;
+        }
 
         if( $sstr->isdeleted ) {
             $evt = _delete_sstr( $editor, $override, $sstr);
@@ -2733,12 +2813,14 @@ sub sum_alter {
     my $editor = new_editor(requestor => $reqr, xact => 1);
     my $override = $self->api_name =~ /override/;
 
-# TODO: permission check
-#        return $editor->event unless
-#            $editor->allowed('UPDATE_COPY', $class->copy_perm_org($vol, $copy));
-
+    my %found_sdist_ids;
     for my $sum (@$sums) {
-        my $sumid = $sum->id;
+        if (!exists($found_sdist_ids{$sum->distribution})) {
+            my $sdist = $editor->retrieve_serial_distribution($sum->distribution) or return $editor->die_event;
+            return $editor->die_event unless
+                $editor->allowed("ADMIN_SERIAL_DISTRIBUTION", $sdist->holding_lib);
+            $found_sdist_ids{$sum->distribution} = 1;
+        }
 
         # XXX: (for now, at least) summaries should be created/deleted by the distribution functions
         if( $sum->isdeleted ) {