[open-ils-commits] r16882 - branches/seials-integration/Open-ILS/src/perlmods/OpenILS/Application (dbwells)

svn at svn.open-ils.org svn at svn.open-ils.org
Thu Jul 8 11:16:53 EDT 2010


Author: dbwells
Date: 2010-07-08 11:16:51 -0400 (Thu, 08 Jul 2010)
New Revision: 16882

Added:
   branches/seials-integration/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm
Log:
Initial commit of the Serial.pm application.  It is mostly basic, still in rough shape, and subject to large changes.  The more complex portions involving predicting and receiving still need more reworking for the new schema.


Added: branches/seials-integration/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm
===================================================================
--- branches/seials-integration/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm	                        (rev 0)
+++ branches/seials-integration/Open-ILS/src/perlmods/OpenILS/Application/Serial.pm	2010-07-08 15:16:51 UTC (rev 16882)
@@ -0,0 +1,1182 @@
+#!/usr/bin/perl
+
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
+
+=head1 NAME
+
+OpenILS::Application::Serial - Performs serials-related tasks such as receiving issues and generating predictions
+
+=head1 SYNOPSIS
+
+TBD
+
+=head1 DESCRIPTION
+
+TBD
+
+=head1 AUTHOR
+
+Dan Wells, dbw2 at calvin.edu
+
+=cut
+
+package OpenILS::Application::Serial;
+
+use strict;
+use warnings;
+
+use OpenILS::Application;
+use base qw/OpenILS::Application/;
+use OpenILS::Application::AppUtils;
+use OpenSRF::AppSession;
+use OpenSRF::Utils::Logger qw($logger);
+use OpenILS::Utils::CStoreEditor q/:funcs/;
+use OpenILS::Utils::MFHD;
+use MARC::File::XML (BinaryEncoding => 'utf8');
+my $U = 'OpenILS::Application::AppUtils';
+my @MFHD_NAMES = ('basic','supplement','index');
+my %MFHD_NAMES_BY_TAG = (  '853' => $MFHD_NAMES[0],
+                        '863' => $MFHD_NAMES[0],
+                        '854' => $MFHD_NAMES[1],
+                        '864' => $MFHD_NAMES[1],
+                        '855' => $MFHD_NAMES[2],
+                        '865' => $MFHD_NAMES[2] );
+
+
+# helper method for conforming dates to ISO8601
+sub _cleanse_dates {
+    my $item = shift;
+    my $fields = shift;
+
+    foreach my $field (@$fields) {
+        $item->$field(OpenSRF::Utils::clense_ISO8601($item->$field)) if $item->$field;
+    }
+    return 0;
+}
+
+
+##########################################################################
+# item methods
+#
+__PACKAGE__->register_method(
+    method    => 'fleshed_item_alter',
+    api_name  => 'open-ils.serial.item.fleshed.batch.update',
+    api_level => 1,
+    argc      => 2,
+    signature => {
+        desc     => 'Receives an array of one or more items and updates the database as needed',
+        'params' => [ {
+                 name => 'authtoken',
+                 desc => 'Authtoken for current user session',
+                 type => 'string'
+            },
+            {
+                 name => 'issuances',
+                 desc => 'Array of fleshed items',
+                 type => 'array'
+            }
+
+        ],
+        'return' => {
+            desc => 'Returns 1 if successful, event if failed',
+            type => 'mixed'
+        }
+    }
+);
+
+sub fleshed_item_alter {
+    my( $self, $conn, $auth, $items ) = @_;
+    return 1 unless ref $items;
+    my( $reqr, $evt ) = $U->checkses($auth);
+    return $evt if $evt;
+    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 $item (@$items) {
+
+        my $itemid = $item->id;
+        $item->editor($editor->requestor->id);
+        $item->edit_date('now');
+
+        if( $item->isdeleted ) {
+            $evt = _delete_item( $editor, $override, $item);
+        } elsif( $item->isnew ) {
+            # TODO: reconsider this
+            # if the item has a new issuance, create the issuance first
+            if ($item->issuance->isnew) {
+                fleshed_issuance_alter($self, $conn, $auth, [$item->issuance]);
+            }
+            _cleanse_dates($item, ['date_expected','date_received']);
+            $evt = _create_item( $editor, $item );
+        } else {
+            _cleanse_dates($item, ['date_expected','date_received']);
+            $evt = _update_item( $editor, $override, $item );
+        }
+    }
+
+    if( $evt ) {
+        $logger->info("fleshed item-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
+        $editor->rollback;
+        return $evt;
+    }
+    $logger->debug("item-alter: done updating item batch");
+    $editor->commit;
+    $logger->info("fleshed item-alter successfully updated ".scalar(@$items)." items");
+    return 1;
+}
+
+sub _delete_item {
+    my ($editor, $override, $item) = @_;
+    $logger->info("item-alter: delete item ".OpenSRF::Utils::JSON->perl2JSON($item));
+    return $editor->event unless $editor->delete_serial_item($item);
+    return 0;
+}
+
+sub _create_item {
+    my ($editor, $item) = @_;
+
+    $item->creator($editor->requestor->id);
+    $item->create_date('now');
+
+    $logger->info("item-alter: new item ".OpenSRF::Utils::JSON->perl2JSON($item));
+    return $editor->event unless $editor->create_serial_item($item);
+    return 0;
+}
+
+sub _update_item {
+    my ($editor, $override, $item) = @_;
+
+    $logger->info("item-alter: retrieving item ".$item->id);
+    my $orig_item = $editor->retrieve_serial_item($item->id);
+
+    $logger->info("item-alter: original item ".OpenSRF::Utils::JSON->perl2JSON($orig_item));
+    $logger->info("item-alter: updated item ".OpenSRF::Utils::JSON->perl2JSON($item));
+    return $editor->event unless $editor->update_serial_item($item);
+    return 0;
+}
+
+__PACKAGE__->register_method(
+    method  => "fleshed_serial_item_retrieve_batch",
+    authoritative => 1,
+    api_name    => "open-ils.serial.item.fleshed.batch.retrieve"
+);
+
+sub fleshed_serial_item_retrieve_batch {
+    my( $self, $client, $ids ) = @_;
+# FIXME: permissions?
+    $logger->info("Fetching fleshed serial items @$ids");
+    return $U->cstorereq(
+        "open-ils.cstore.direct.serial.item.search.atomic",
+        { id => $ids },
+        { flesh => 2,
+          flesh_fields => {sitem => [ qw/issuance creator editor stream unit notes/ ], sstr => ["distribution"], sunit => ["call_number"], siss => [qw/creator editor subscription/]}
+        });
+}
+
+
+##########################################################################
+# issuance methods
+#
+__PACKAGE__->register_method(
+    method    => 'fleshed_issuance_alter',
+    api_name  => 'open-ils.serial.issuance.fleshed.batch.update',
+    api_level => 1,
+    argc      => 2,
+    signature => {
+        desc     => 'Receives an array of one or more issuances and updates the database as needed',
+        'params' => [ {
+                 name => 'authtoken',
+                 desc => 'Authtoken for current user session',
+                 type => 'string'
+            },
+            {
+                 name => 'issuances',
+                 desc => 'Array of fleshed issuances',
+                 type => 'array'
+            }
+
+        ],
+        'return' => {
+            desc => 'Returns 1 if successful, event if failed',
+            type => 'mixed'
+        }
+    }
+);
+
+sub fleshed_issuance_alter {
+    my( $self, $conn, $auth, $issuances ) = @_;
+    return 1 unless ref $issuances;
+    my( $reqr, $evt ) = $U->checkses($auth);
+    return $evt if $evt;
+    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));
+
+    for my $issuance (@$issuances) {
+        my $issuanceid = $issuance->id;
+        $issuance->editor($editor->requestor->id);
+        $issuance->edit_date('now');
+
+        if( $issuance->isdeleted ) {
+            $evt = _delete_issuance( $editor, $override, $issuance);
+        } elsif( $issuance->isnew ) {
+            _cleanse_dates($issuance, ['date_published']);
+            $evt = _create_issuance( $editor, $issuance );
+        } else {
+            _cleanse_dates($issuance, ['date_published']);
+            $evt = _update_issuance( $editor, $override, $issuance );
+        }
+    }
+
+    if( $evt ) {
+        $logger->info("fleshed issuance-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
+        $editor->rollback;
+        return $evt;
+    }
+    $logger->debug("issuance-alter: done updating issuance batch");
+    $editor->commit;
+    $logger->info("fleshed issuance-alter successfully updated ".scalar(@$issuances)." issuances");
+    return 1;
+}
+
+sub _delete_issuance {
+    my ($editor, $override, $issuance) = @_;
+    $logger->info("issuance-alter: delete issuance ".OpenSRF::Utils::JSON->perl2JSON($issuance));
+    return $editor->event unless $editor->delete_serial_issuance($issuance);
+    return 0;
+}
+
+sub _create_issuance {
+    my ($editor, $issuance) = @_;
+
+    $issuance->creator($editor->requestor->id);
+    $issuance->create_date('now');
+
+    $logger->info("issuance-alter: new issuance ".OpenSRF::Utils::JSON->perl2JSON($issuance));
+    return $editor->event unless $editor->create_serial_issuance($issuance);
+    return 0;
+}
+
+sub _update_issuance {
+    my ($editor, $override, $issuance) = @_;
+
+    $logger->info("issuance-alter: retrieving issuance ".$issuance->id);
+    my $orig_issuance = $editor->retrieve_serial_issuance($issuance->id);
+
+    $logger->info("issuance-alter: original issuance ".OpenSRF::Utils::JSON->perl2JSON($orig_issuance));
+    $logger->info("issuance-alter: updated issuance ".OpenSRF::Utils::JSON->perl2JSON($issuance));
+    return $editor->event unless $editor->update_serial_issuance($issuance);
+    return 0;
+}
+
+
+##########################################################################
+# unit methods
+#
+__PACKAGE__->register_method(
+    method    => 'fleshed_sunit_alter',
+    api_name  => 'open-ils.serial.sunit.fleshed.batch.update',
+    api_level => 1,
+    argc      => 2,
+    signature => {
+        desc     => 'Receives an array of one or more Units and updates the database as needed',
+        'params' => [ {
+                 name => 'authtoken',
+                 desc => 'Authtoken for current user session',
+                 type => 'string'
+            },
+            {
+                 name => 'sunits',
+                 desc => 'Array of fleshed Units',
+                 type => 'array'
+            }
+
+        ],
+        'return' => {
+            desc => 'Returns 1 if successful, event if failed',
+            type => 'mixed'
+        }
+    }
+);
+
+sub fleshed_sunit_alter {
+    my( $self, $conn, $auth, $sunits ) = @_;
+    return 1 unless ref $sunits;
+    my( $reqr, $evt ) = $U->checkses($auth);
+    return $evt if $evt;
+    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));
+
+    for my $sunit (@$sunits) {
+        if( $sunit->isdeleted ) {
+            $evt = _delete_sunit( $editor, $override, $sunit );
+        } else {
+            $sunit->default_location( $sunit->default_location->id ) if ref $sunit->default_location;
+
+            if( $sunit->isnew ) {
+                $evt = _create_sunit( $editor, $sunit );
+            } else {
+                $evt = _update_sunit( $editor, $override, $sunit );
+            }
+        }
+    }
+
+    if( $evt ) {
+        $logger->info("fleshed sunit-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
+        $editor->rollback;
+        return $evt;
+    }
+    $logger->debug("sunit-alter: done updating sunit batch");
+    $editor->commit;
+    $logger->info("fleshed sunit-alter successfully updated ".scalar(@$sunits)." Units");
+    return 1;
+}
+
+sub _delete_sunit {
+    my ($editor, $override, $sunit) = @_;
+    $logger->info("sunit-alter: delete sunit ".OpenSRF::Utils::JSON->perl2JSON($sunit));
+    return $editor->event unless $editor->delete_serial_unit($sunit);
+    return 0;
+}
+
+sub _create_sunit {
+    my ($editor, $sunit) = @_;
+
+    $logger->info("sunit-alter: new Unit ".OpenSRF::Utils::JSON->perl2JSON($sunit));
+    return $editor->event unless $editor->create_serial_unit($sunit);
+    return 0;
+}
+
+sub _update_sunit {
+    my ($editor, $override, $sunit) = @_;
+
+#    my $evt;
+#    my $org = (ref $copy->circ_lib) ? $copy->circ_lib->id : $copy->circ_lib;
+#    return $evt if ( $evt = OpenILS::Application::Cat::AssetCommon->org_cannot_have_vols($editor, $org) );
+
+    $logger->info("sunit-alter: retrieving sunit ".$sunit->id);
+    my $orig_sunit = $editor->retrieve_serial_unit($sunit->id);
+
+    $logger->info("sunit-alter: original sunit ".OpenSRF::Utils::JSON->perl2JSON($orig_sunit));
+    $logger->info("sunit-alter: updated sunit ".OpenSRF::Utils::JSON->perl2JSON($sunit));
+    return $editor->event unless $editor->update_serial_unit($sunit);
+    return 0;
+}
+
+
+##########################################################################
+# predict and receive methods
+#
+__PACKAGE__->register_method(
+    method    => 'generate_predictions',
+    api_name  => 'open-ils.serial.generate_predictions',
+    api_level => 1,
+    argc      => 1,
+    signature => {
+        desc     => 'Receives an sre (serial record entry) id and returns an array ref of predicted issuances',
+        'params' => [ {
+                 name => 'sre_id',
+                 desc => 'Serial Record Entry ID',
+                 type => 'integer'
+            }
+        ],
+        'return' => {
+            desc => 'Returns predicted issuances',
+            type => 'array'
+        }
+    }
+);
+
+sub generate_predictions {
+    my ($self, $conn, $authtoken, $args) = @_;
+
+    my $editor = OpenILS::Utils::CStoreEditor->new();
+    if (!exists($args->{sre_id})) { # lookup by sdist_id instead
+        my $sdist = $editor->retrieve_serial_distribution([$args->{sdist_id}]);
+        $args->{sre_id} = $sdist->record_entry;
+    }
+    #return $args->{sre_id};
+    my $sre = $editor->retrieve_serial_record_entry([$args->{sre_id}]);
+
+    #return $sre->marc;
+
+    #convert from marc_xml to marc
+    my $marc = MARC::Record->new_from_xml($sre->marc);
+
+    #turn into MFHD record object
+    my $mfhd = MFHD->new($marc);
+
+    my @predictions;
+    # TODO: consider support for predicting supplements/indexes (854/855)
+    my $tag = '853';
+    my @active_captions = $mfhd->active_captions($tag);
+    foreach my $caption (@active_captions) {
+        my $options = {
+                'caption' => $caption,
+                'num_to_predict' => $args->{num_to_predict},
+                'last_rec_date' => $args->{last_rec_date}
+                };
+        if ($args->{from_last_received}) {
+            my $last_received = $editor->search_serial_issuance([
+                {   'holding_type' => $MFHD_NAMES_BY_TAG{$tag},
+                    'holding_link_id' => $caption->link_id,
+                    'distribution' => $args->{sdist_id}},
+                {limit => 1, order_by => { siss => "date_expected DESC" }}]
+                );
+            if ($last_received->[0]) {
+                $options->{last_rec_date} = $last_received->[0]->date_expected;
+                $options->{predict_from} = _revive_holding($mfhd, $last_received->[0]->holding_code);
+            }
+        }
+        push( @predictions, _generate_issuance_values($mfhd, $options) );
+    }
+
+    my @issuances;
+    foreach my $prediction (@predictions) {
+        my $issuance = new Fieldmapper::serial::issuance;
+        $issuance->isnew(1);
+        $issuance->holding_link_id($prediction->[0]);
+        $issuance->label($prediction->[1]);
+        $issuance->date_published($prediction->[2]);
+        $issuance->date_expected($prediction->[3]);
+        $issuance->holding_code(OpenSRF::Utils::JSON->perl2JSON($prediction->[4]));
+        $issuance->holding_type($prediction->[5]);
+        push (@issuances, $issuance);
+    }
+
+    return \@issuances;
+}
+
+#
+# _generate_issuance_values() is an initial attempt at a function which can be used
+# to populate an issuance table with a list of predicted issues.  It accepts
+# 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
+#
+# The basic method is to first convert to a single holding if compressed, then
+# increment the holding and save the resulting values to @issuances.
+# 
+# returns @issuance_values, an array of array refs containing (link id, formatted
+# label, formatted chronology date, formatted estimated arrival date, and an
+# array ref of holding subfields as (key, value, key, value ...)) (not a hash
+# to protect order and possible duplicate keys).
+#
+sub _generate_issuance_values {
+    my ($mfhd, $options) = @_;
+    my $caption = $options->{caption};
+    my $num_to_predict = $options->{num_to_predict};
+    my $last_rec_date = $options->{last_rec_date};   # expected or actual, according to preference
+    my $predict_from = $options->{predict_from};   # optional issuance to predict from
+
+    # TODO: add support for predicting serials with no chronology by passing in
+    # a last_pub_date option?
+
+    my $strp = new DateTime::Format::Strptime(pattern => '%F');
+
+    my $receival_date = $strp->parse_datetime($last_rec_date);
+
+    my $htag    = $caption->tag;
+    $htag =~ s/^85/86/;
+    my $link_id = $caption->link_id;
+    if(!$predict_from) {
+        my @holdings = $mfhd->holdings($htag, $link_id);
+        my $last_holding = $holdings[-1];
+
+        if ($last_holding->is_compressed) {
+            $last_holding->compressed_to_last; # convert to last in range
+        }
+        $predict_from = $last_holding;
+    }
+
+    my $pub_date  = $strp->parse_datetime($predict_from->chron_to_date);
+    my $date_diff = $receival_date - $pub_date;
+
+    $predict_from->notes('public',  []);
+# add a note marker for system use
+    $predict_from->notes('private', ['AUTOGEN']);
+
+    my @issuance_values;
+    my @predictions = $mfhd->generate_predictions({'base_holding' => $predict_from, 'num_to_predict' => $num_to_predict});
+    foreach my $prediction (@predictions) {
+        $pub_date = $strp->parse_datetime($prediction->chron_to_date);
+        my $arrival_date = $pub_date + $date_diff;
+        push(
+                @issuance_values,
+                [
+                    $link_id,
+                    $prediction->format,
+                    $pub_date->strftime('%F'),
+                    $arrival_date->strftime('%F'),
+                    [$htag,$prediction->indicator(1),$prediction->indicator(2),$prediction->subfields_list],
+                    $MFHD_NAMES_BY_TAG{$caption->tag}
+                ]
+            );
+    }
+
+    return @issuance_values;
+}
+
+sub _revive_holding {
+    my $mfhd = shift;
+    my $holding_code = shift;
+
+    # build MARC::Field
+    my $holding_parts = OpenSRF::Utils::JSON->JSON2perl($holding_code);
+    my $issuance_holding = new MARC::Field(@$holding_parts);
+    # fetch matching captions
+    my $captag = $issuance_holding->tag;
+    $captag =~ s/^86/85/;
+    my $captions_ref = $mfhd->captions($captag, 'hashref');
+    # build MFHD::Holding
+    my $link_subfield = $issuance_holding->subfield('8');
+    my ($link_id, $seqno) = split(/\./, $link_subfield);
+    return new MFHD::Holding($seqno, $issuance_holding, $captions_ref->{$link_id});
+}
+
+__PACKAGE__->register_method(
+    method    => 'receive_issuances',
+    api_name  => 'open-ils.serial.receive_issuances',
+    api_level => 1,
+    argc      => 1,
+    signature => {
+        desc     => 'Marks an issuance as received, updates the shelving unit (creating a new shelving unit if needed), and updates the underlying MFHD record',
+        'params' => [ {
+                 name => 'issuances',
+                 desc => 'array of Issuance objects',
+                 type => 'array'
+            }
+        ],
+        'return' => {
+            desc => 'Returns number of received issuances',
+            type => 'int'
+        }
+    }
+);
+
+sub receive_issuances {
+    my ($self, $conn, $auth, $issuances) = @_;
+
+    my $last_distribution;
+    my $last_mfhd;
+    my %sres_to_save;
+    my %mfhds_to_save;
+    my( $reqr, $evt ) = $U->checkses($auth);
+    return $evt if $evt;
+    my $editor = new_editor(requestor => $reqr, xact => 1);
+    foreach my $issuance (@$issuances) {
+        # unflesh shelving unit if fleshed
+        $issuance->shelving_unit( $issuance->shelving_unit->id ) if ref($issuance->shelving_unit);
+        $issuance->distribution( $issuance->distribution->id ) if ref($issuance->distribution);
+
+        $issuance->copies_received($issuance->copies_received + 1);
+        $issuance->copies_expected($issuance->copies_expected - 1);
+        $issuance->date_received('now');
+
+        # create shelving unit if needed
+        if ($issuance->shelving_unit == -1) { # create by "volume" (first issuance division)
+        #TODO
+        } elsif ($issuance->shelving_unit == -2) { # create by "issue" (second issuance division)
+        #TODO
+        }
+
+        my $mfhd;
+        my $sre;
+        if ($issuance->distribution == $last_distribution) {
+            # use cached record
+            $mfhd = $last_mfhd;
+        } else { # get MFHD record
+            my $sdist = $editor->retrieve_serial_distribution([$issuance->distribution]);
+            $sre = $editor->retrieve_serial_record_entry([$sdist->record_entry]);
+
+            #convert from marc_xml to marc
+            my $marc = MARC::Record->new_from_xml($sre->marc);
+
+            #turn into MFHD record object
+            $mfhd = MFHD->new($marc);
+            $sres_to_save{$sre->id} = $sre;
+            $mfhds_to_save{$sre->id} = $mfhd;
+        }
+
+#        # build MARC::Field
+#        my $holding_parts = OpenSRF::Utils::JSON->JSON2perl($issuance->holding_code);
+#        my $issuance_holding = new MARC::Field(@$holding_parts);
+#        # fetch matching captions
+#        my $captag = $issuance_holding->tag;
+#        $captag =~ s/^86/85/;
+#        my $captions_ref = $mfhd->captions($captag, 'hashref');
+#        # build MFHD::Holding
+#        my $link_subfield = $issuance_holding->subfield('8');
+#        my ($link_id, $seqno) = split(/\./, $link_subfield);
+#        $issuance_holding = new MFHD::Holding($seqno, $issuance_holding, $captions_ref->{$link_id});
+        my $issuance_holding = _revive_holding($mfhd, $issuance->holding_code);
+        
+        # get all current holdings for this linked caption
+#        my @curr_holdings = $mfhd->holdings($issuance_holding->tag, $link_id);
+        my @curr_holdings = $mfhd->holdings($issuance_holding->tag, $issuance_holding->caption->link_id);
+        # short-circuit logic : if holding is the next one, increment the last current holding
+        my $next_holding_values = $curr_holdings[-1]->next;
+        if ($next_holding_values and $issuance_holding->matches($next_holding_values)) {
+            $curr_holdings[-1]->extend;
+        } else { # not the next expected, do full replacement
+            $mfhd->append_fields($issuance_holding);
+#            my @updated_holdings = $mfhd->get_compressed_holdings($captions_ref->{$link_id});
+            my @updated_holdings = $mfhd->get_compressed_holdings($issuance_holding->caption);
+            # set reference point to top of current holdings
+            my $marker_field = MARC::Field->new(500, '', '','a' => 'Temporary Marker'); 
+            $mfhd->insert_fields_before($curr_holdings[0], $marker_field);
+            foreach my $holding (@curr_holdings) {
+                $mfhd->delete_field($holding);
+            }
+            $mfhd->delete_field($issuance_holding);
+            $mfhd->insert_fields_before($marker_field, @updated_holdings);
+            # delete reference point
+            $mfhd->delete_field($marker_field);
+        }   
+
+        $last_distribution = $issuance->distribution;
+        $last_mfhd = $mfhd;
+        _update_issuance($editor, undef, $issuance);
+    }
+
+    foreach my $sre_id (keys %sres_to_save) {
+        #TODO: update '005' to current date
+        my $sre = $sres_to_save{$sre_id};
+        (my $xml = $mfhds_to_save{$sre_id}->as_xml_record()) =~ s/\n//sog;
+        $xml =~ s/^<\?xml.+\?\s*>//go;
+        $sre->marc($xml);
+        $sre->ischanged(1);
+        $editor->update_serial_record_entry($sre);
+        #return ($sre->record);
+    }
+
+    #return OpenSRF::Utils::JSON->perl2JSON($last_mfhd);
+
+    $editor->commit;
+    return scalar @$issuances;
+}
+
+
+##########################################################################
+# note methods
+#
+__PACKAGE__->register_method(
+    method      => 'fetch_notes',
+    api_name        => 'open-ils.serial.item_note.retrieve.all',
+    signature   => q/
+        Returns an array of copy note objects.  
+        @param args A named hash of parameters including:
+            authtoken   : Required if viewing non-public notes
+            item_id      : The id of the item whose notes we want to retrieve
+            pub         : True if all the caller wants are public notes
+        @return An array of note objects
+    /
+);
+
+__PACKAGE__->register_method(
+    method      => 'fetch_notes',
+    api_name        => 'open-ils.serial.subscription_note.retrieve.all',
+    signature   => q/
+        Returns an array of copy note objects.  
+        @param args A named hash of parameters including:
+            authtoken       : Required if viewing non-public notes
+            subscription_id : The id of the item whose notes we want to retrieve
+            pub             : True if all the caller wants are public notes
+        @return An array of note objects
+    /
+);
+
+# TODO: revisit this method to consider replacing cstore direct calls
+sub fetch_notes {
+    my( $self, $connection, $args ) = @_;
+    
+    $self->api_name =~ /serial\.(\w*)_note/;
+    my $type = $1;
+
+    my $id = $$args{object_id};
+    my $authtoken = $$args{authtoken};
+    my( $r, $evt);
+
+    if( $$args{pub} ) {
+        return $U->cstorereq(
+            'open-ils.cstore.direct.serial.'.$type.'_note.search.atomic',
+            { $type => $id, pub => 't' } );
+    } else {
+        # FIXME: restore perm check
+        # ( $r, $evt ) = $U->checksesperm($authtoken, 'VIEW_COPY_NOTES');
+        # return $evt if $evt;
+        return $U->cstorereq(
+            'open-ils.cstore.direct.serial.'.$type.'_note.search.atomic', {$type => $id} );
+    }
+
+    return undef;
+}
+
+__PACKAGE__->register_method(
+    method      => 'create_note',
+    api_name        => 'open-ils.serial.item_note.create',
+    signature   => q/
+        Creates a new item note
+        @param authtoken The login session key
+        @param note The note object to create
+        @return The id of the new note object
+    /
+);
+
+__PACKAGE__->register_method(
+    method      => 'create_note',
+    api_name        => 'open-ils.serial.subscription_note.create',
+    signature   => q/
+        Creates a new subscription note
+        @param authtoken The login session key
+        @param note The note object to create
+        @return The id of the new note object
+    /
+);
+
+sub create_note {
+    my( $self, $connection, $authtoken, $note ) = @_;
+
+    $self->api_name =~ /serial\.(\w*)_note/;
+    my $type = $1;
+
+    my $e = new_editor(xact=>1, authtoken=>$authtoken);
+    return $e->event unless $e->checkauth;
+
+    # FIXME: restore permission support
+#    my $item = $e->retrieve_serial_item(
+#        [
+#            $note->item
+#        ]
+#    );
+#
+#    return $e->event unless
+#        $e->allowed('CREATE_COPY_NOTE', $item->call_number->owning_lib);
+
+    $note->create_date('now');
+    $note->creator($e->requestor->id);
+    $note->pub( ($U->is_true($note->pub)) ? 't' : 'f' );
+    $note->clear_id;
+
+    my $method = "create_serial_${type}_note";
+    $e->$method($note) or return $e->event;
+    $e->commit;
+    return $note->id;
+}
+
+__PACKAGE__->register_method(
+    method      => 'delete_note',
+    api_name        =>  'open-ils.serial.item_note.delete',
+    signature   => q/
+        Deletes an existing item note
+        @param authtoken The login session key
+        @param noteid The id of the note to delete
+        @return 1 on success - Event otherwise.
+        /
+);
+
+__PACKAGE__->register_method(
+    method      => 'delete_note',
+    api_name        =>  'open-ils.serial.subscription_note.delete',
+    signature   => q/
+        Deletes an existing subscription note
+        @param authtoken The login session key
+        @param noteid The id of the note to delete
+        @return 1 on success - Event otherwise.
+        /
+);
+
+sub delete_note {
+    my( $self, $conn, $authtoken, $noteid ) = @_;
+
+    $self->api_name =~ /serial\.(\w*)_note/;
+    my $type = $1;
+
+    my $e = new_editor(xact=>1, authtoken=>$authtoken);
+    return $e->die_event unless $e->checkauth;
+
+    my $note = $e->retrieve_serial_item_note([
+        $noteid,
+    ]) or return $e->die_event;
+
+# FIXME: restore permissions check
+#    if( $note->creator ne $e->requestor->id ) {
+#        return $e->die_event unless
+#            $e->allowed('DELETE_COPY_NOTE', $note->item->call_number->owning_lib);
+#    }
+
+    my $method = "delete_serial_${type}_note";
+    $e->$method($note) or return $e->die_event;
+    $e->commit;
+    return 1;
+}
+
+
+##########################################################################
+# subscription methods
+#
+__PACKAGE__->register_method(
+    method    => 'fleshed_ssub_alter',
+    api_name  => 'open-ils.serial.subscription.fleshed.batch.update',
+    api_level => 1,
+    argc      => 2,
+    signature => {
+        desc     => 'Receives an array of one or more subscriptions and updates the database as needed',
+        'params' => [ {
+                 name => 'authtoken',
+                 desc => 'Authtoken for current user session',
+                 type => 'string'
+            },
+            {
+                 name => 'subscriptions',
+                 desc => 'Array of fleshed subscriptions',
+                 type => 'array'
+            }
+
+        ],
+        'return' => {
+            desc => 'Returns 1 if successful, event if failed',
+            type => 'mixed'
+        }
+    }
+);
+
+sub fleshed_ssub_alter {
+    my( $self, $conn, $auth, $ssubs ) = @_;
+    return 1 unless ref $ssubs;
+    my( $reqr, $evt ) = $U->checkses($auth);
+    return $evt if $evt;
+    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 $ssubid = $ssub->id;
+
+        if( $ssub->isdeleted ) {
+            $evt = _delete_ssub( $editor, $override, $ssub);
+        } elsif( $ssub->isnew ) {
+            _cleanse_dates($ssub, ['start_date','end_date']);
+            $evt = _create_ssub( $editor, $ssub );
+        } else {
+            _cleanse_dates($ssub, ['start_date','end_date']);
+            $evt = _update_ssub( $editor, $override, $ssub );
+        }
+    }
+
+    if( $evt ) {
+        $logger->info("fleshed subscription-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
+        $editor->rollback;
+        return $evt;
+    }
+    $logger->debug("subscription-alter: done updating subscription batch");
+    $editor->commit;
+    $logger->info("fleshed subscription-alter successfully updated ".scalar(@$ssubs)." subscriptions");
+    return 1;
+}
+
+sub _delete_ssub {
+    my ($editor, $override, $ssub) = @_;
+    $logger->info("subscription-alter: delete subscription ".OpenSRF::Utils::JSON->perl2JSON($ssub));
+    return $editor->event unless $editor->delete_serial_subscription($ssub);
+    return 0;
+}
+
+sub _create_ssub {
+    my ($editor, $ssub) = @_;
+
+    $logger->info("subscription-alter: new subscription ".OpenSRF::Utils::JSON->perl2JSON($ssub));
+    return $editor->event unless $editor->create_serial_subscription($ssub);
+    return 0;
+}
+
+sub _update_ssub {
+    my ($editor, $override, $ssub) = @_;
+
+    $logger->info("subscription-alter: retrieving subscription ".$ssub->id);
+    my $orig_ssub = $editor->retrieve_serial_subscription($ssub->id);
+
+    $logger->info("subscription-alter: original subscription ".OpenSRF::Utils::JSON->perl2JSON($orig_ssub));
+    $logger->info("subscription-alter: updated subscription ".OpenSRF::Utils::JSON->perl2JSON($ssub));
+    return $editor->event unless $editor->update_serial_subscription($ssub);
+    return 0;
+}
+
+__PACKAGE__->register_method(
+    method  => "fleshed_serial_subscription_retrieve_batch",
+    authoritative => 1,
+    api_name    => "open-ils.serial.subscription.fleshed.batch.retrieve"
+);
+
+sub fleshed_serial_subscription_retrieve_batch {
+    my( $self, $client, $ids ) = @_;
+# FIXME: permissions?
+    $logger->info("Fetching fleshed subscriptions @$ids");
+    return $U->cstorereq(
+        "open-ils.cstore.direct.serial.subscription.search.atomic",
+        { id => $ids },
+        { flesh => 1,
+          flesh_fields => {ssub => [ qw/owning_lib notes/ ]}
+        });
+}
+
+__PACKAGE__->register_method(
+	method	=> "retrieve_sub_tree",
+    authoritative => 1,
+	api_name	=> "open-ils.serial.subscription_tree.retrieve"
+);
+
+__PACKAGE__->register_method(
+	method	=> "retrieve_sub_tree",
+	api_name	=> "open-ils.serial.subscription_tree.global.retrieve"
+);
+
+# user_session may be null/undef
+sub retrieve_sub_tree {
+
+	my( $self, $client, $user_session, $docid, @org_ids ) = @_;
+
+	if(ref($org_ids[0])) { @org_ids = @{$org_ids[0]}; }
+
+	$docid = "$docid";
+
+	# TODO: permission support
+	if(!@org_ids and $user_session) {
+		my $user_obj = 
+			OpenILS::Application::AppUtils->check_user_session( $user_session ); #throws EX on error
+			@org_ids = ($user_obj->home_ou);
+	}
+
+	if( $self->api_name =~ /global/ ) {
+		return _build_subs_list( { record_entry => $docid } ); # TODO: filter for !deleted, or active?
+
+	} else {
+
+		my @all_subs;
+		for my $orgid (@org_ids) {
+			my $subs = _build_subs_list( 
+					{ record_entry => $docid, owning_lib => $orgid } );# TODO: filter for !deleted, or active?
+			push( @all_subs, @$subs );
+		}
+		
+		return \@all_subs;
+	}
+
+	return undef;
+}
+
+sub _build_subs_list {
+	my $search_hash = shift;
+
+	#$search_hash->{deleted} = 'f';
+	my $e = new_editor();
+
+	my $subs = $e->search_serial_subscription([$search_hash, { 'order_by' => {'ssub' => 'id'} }]);
+
+	my @built_subs;
+
+	for my $sub (@$subs) {
+
+        # TODO: filter on !deleted?
+		my $dists = $e->search_serial_distribution(
+            [{ subscription => $sub->id }, { 'order_by' => {'sdist' => 'label'} }]
+            );
+
+		#$dists = [ sort { $a->label cmp $b->label } @$dists  ];
+
+#		for my $dist (@$dists) {
+#			if( $c->status == OILS_COPY_STATUS_CHECKED_OUT ) {
+#				$c->circulations(
+#					$e->search_action_circulation(
+#						[
+#							{ target_copy => $c->id },
+#							{
+#								order_by => { circ => 'xact_start desc' },
+#								limit => 1
+#							}
+#						]
+#					)
+#				)
+#			}
+#		}
+
+		$sub->distributions($dists);
+        
+        # TODO: filter on !deleted?
+		my $issuances = $e->search_serial_issuance(
+			[{ subscription => $sub->id }, { 'order_by' => {'siss' => 'label'} }]
+            );
+
+		#$issuances = [ sort { $a->label cmp $b->label } @$issuances  ];
+		$sub->issuances($issuances);
+
+		my $scaps = $e->search_serial_caption_and_pattern(
+			{ subscription => $sub->id }); # TODO: filter on !deleted?
+
+		$scaps = [ sort { $a->id cmp $b->id } @$scaps  ];
+		$sub->scaps($scaps);
+		push( @built_subs, $sub );
+	}
+
+	return \@built_subs;
+
+}
+
+__PACKAGE__->register_method(
+    method  => "subscription_orgs_for_title",
+    authoritative => 1,
+    api_name    => "open-ils.serial.subscription.retrieve_orgs_by_title"
+);
+
+sub subscription_orgs_for_title {
+    my( $self, $client, $record_id ) = @_;
+
+    my $subs = $U->simple_scalar_request(
+        "open-ils.cstore",
+        "open-ils.cstore.direct.serial.subscription.search.atomic",
+        { record_entry => $record_id }); # TODO: filter on !deleted?
+
+    my $orgs = { map {$_->owning_lib => 1 } @$subs };
+    return [ keys %$orgs ];
+}
+
+
+##########################################################################
+# distribution methods
+#
+__PACKAGE__->register_method(
+    method    => 'fleshed_sdist_alter',
+    api_name  => 'open-ils.serial.distribution.fleshed.batch.update',
+    api_level => 1,
+    argc      => 2,
+    signature => {
+        desc     => 'Receives an array of one or more distributions and updates the database as needed',
+        'params' => [ {
+                 name => 'authtoken',
+                 desc => 'Authtoken for current user session',
+                 type => 'string'
+            },
+            {
+                 name => 'distributions',
+                 desc => 'Array of fleshed distributions',
+                 type => 'array'
+            }
+
+        ],
+        'return' => {
+            desc => 'Returns 1 if successful, event if failed',
+            type => 'mixed'
+        }
+    }
+);
+
+sub fleshed_sdist_alter {
+    my( $self, $conn, $auth, $sdists ) = @_;
+    return 1 unless ref $sdists;
+    my( $reqr, $evt ) = $U->checkses($auth);
+    return $evt if $evt;
+    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;
+
+        if( $sdist->isdeleted ) {
+            $evt = _delete_sdist( $editor, $override, $sdist);
+        } elsif( $sdist->isnew ) {
+            $evt = _create_sdist( $editor, $sdist );
+        } else {
+            $evt = _update_sdist( $editor, $override, $sdist );
+        }
+    }
+
+    if( $evt ) {
+        $logger->info("fleshed distribution-alter failed with event: ".OpenSRF::Utils::JSON->perl2JSON($evt));
+        $editor->rollback;
+        return $evt;
+    }
+    $logger->debug("distribution-alter: done updating distribution batch");
+    $editor->commit;
+    $logger->info("fleshed distribution-alter successfully updated ".scalar(@$sdists)." distributions");
+    return 1;
+}
+
+sub _delete_sdist {
+    my ($editor, $override, $sdist) = @_;
+    $logger->info("distribution-alter: delete distribution ".OpenSRF::Utils::JSON->perl2JSON($sdist));
+    return $editor->event unless $editor->delete_serial_distribution($sdist);
+    return 0;
+}
+
+sub _create_sdist {
+    my ($editor, $sdist) = @_;
+
+    $logger->info("distribution-alter: new distribution ".OpenSRF::Utils::JSON->perl2JSON($sdist));
+    return $editor->event unless $editor->create_serial_distribution($sdist);
+    return 0;
+}
+
+sub _update_sdist {
+    my ($editor, $override, $sdist) = @_;
+
+    $logger->info("distribution-alter: retrieving distribution ".$sdist->id);
+    my $orig_sdist = $editor->retrieve_serial_distribution($sdist->id);
+
+    $logger->info("distribution-alter: original distribution ".OpenSRF::Utils::JSON->perl2JSON($orig_sdist));
+    $logger->info("distribution-alter: updated distribution ".OpenSRF::Utils::JSON->perl2JSON($sdist));
+    return $editor->event unless $editor->update_serial_distribution($sdist);
+    return 0;
+}
+
+__PACKAGE__->register_method(
+    method  => "fleshed_serial_distribution_retrieve_batch",
+    authoritative => 1,
+    api_name    => "open-ils.serial.distribution.fleshed.batch.retrieve"
+);
+
+sub fleshed_serial_distribution_retrieve_batch {
+    my( $self, $client, $ids ) = @_;
+# FIXME: permissions?
+    $logger->info("Fetching fleshed distributions @$ids");
+    return $U->cstorereq(
+        "open-ils.cstore.direct.serial.distribution.search.atomic",
+        { id => $ids },
+        { flesh => 1,
+          flesh_fields => {sdist => [ qw/ holding_lib receive_call_number receive_unit_template bind_call_number bind_unit_template / ]}
+        });
+}
+
+1;



More information about the open-ils-commits mailing list