[open-ils-commits] [GIT] Evergreen ILS branch master updated. 0438399769af9d9747a0185a8ae73a3cc944c00d

Evergreen Git git at git.evergreen-ils.org
Tue Jul 24 12:44:44 EDT 2012


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "Evergreen ILS".

The branch, master has been updated
       via  0438399769af9d9747a0185a8ae73a3cc944c00d (commit)
      from  b1c9f9c8b030060aa4df4ebc98c37cc05f5f6207 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 0438399769af9d9747a0185a8ae73a3cc944c00d
Author: Bill Erickson <berick at esilibrary.com>
Date:   Mon Jul 23 10:44:41 2012 -0400

    ACQ order record fetcher and uploader script
    
    Some ACQ vendors support delivering MARC order record files directly
    from their order system to an ILS via FTP.  (I've heard this called
    "one-click" ordering in the past).  This commit includes a script to
    seek out such order record files and pass them on to the Acquisitions
    service for PO creation and potential activation.
    
    The script supports a number of options, configured in opensrf.xml,
    including which Vandelay (record import) options to use during record
    import/merge/overlay.  See opensrf.xml.example for details.
    
    Example:
    
    ./acq_order_reader.pl \
        --user admin \
        --password demo123 \
        -poll-interval 3 \
        --debug &
    
    Signed-off-by: Bill Erickson <berick at esilibrary.com>
    Signed-off-by: Mike Rylander <mrylander at gmail.com>

diff --git a/Open-ILS/examples/opensrf.xml.example b/Open-ILS/examples/opensrf.xml.example
index ea6f18b..b444cf3 100644
--- a/Open-ILS/examples/opensrf.xml.example
+++ b/Open-ILS/examples/opensrf.xml.example
@@ -271,6 +271,54 @@ vim:et:ts=4:sw=4:
 
         </added_content>
 
+        <!-- Config section for acq_order_reader.pl script.
+             It reads MARC order record files from disk (presumably
+             an FTP destination) and pushes the order records into ACQ.
+             THIS IS NOT EDI. -->
+        <acq_order_reader>
+
+            <!-- Root directory for all FTP'd ACQ order record files .
+                 If the script is configured to talk to a remote acq server,
+                 this directory has to be a read/write NFS share.  -->
+            <base_dir>/openils/var/data/acq_orders/</base_dir>
+
+            <!-- any files found in the shared subdir must be inspected
+                 (e.g. file name prefix) to determine the provider. -->
+            <shared_subdir>ALL</shared_subdir><!-- SUPPORT PENDING -->
+
+            <!-- providers that don't provide a mechanism to inspect the file
+                 have to push their files to provider-specific locations -->
+            <provider>
+                <ordering_agency>BR1</ordering_agency> <!-- who gets/manages the order -->
+                <provider_code>BAB</provider_code>   
+                <provider_owner>CONS</provider_owner>  <!-- provider provider_owner; org unit shortname -->
+                <subdir>CONS-BAB</subdir> <!-- file directory;  full path = base_dir + subdir -->
+                <activate_po>false</activate_po> <!-- activate PO at upload? -->
+                <vandelay>
+                    <import_no_match>true</import_no_match>
+                    <!-- Most Vandelay options are supported.  For bools, use true/false.
+                        match_quality_ratio 
+                        match_set 
+                        bib_source 
+                        merge_profile
+                        create_assets
+                        import_no_match 
+                        auto_overlay_exact 
+                        auto_overlay_1match 
+                        auto_overlay_best_match
+                    -->
+                </vandelay>
+            </provider>
+
+            <!-- Add more as needed...
+            <provider>
+                ...
+            </provider>
+            -->
+
+        </acq_order_reader>
+
+
         <!-- no apps are enabled globally by default -->
         <activeapps/> 
 
diff --git a/Open-ILS/src/support-scripts/acq_order_reader.pl b/Open-ILS/src/support-scripts/acq_order_reader.pl
new file mode 100755
index 0000000..ba0e2aa
--- /dev/null
+++ b/Open-ILS/src/support-scripts/acq_order_reader.pl
@@ -0,0 +1,381 @@
+#!/usr/bin/perl
+# Copyright (C) 2012 Equinox Software, Inc.
+# Author: Bill Erickson <erickson at esilibrary.com>
+#
+# 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.
+
+use strict; use warnings;
+use MARC::Record;
+use MARC::Batch;
+use MARC::File::XML ( BinaryEncoding => 'UTF-8' );
+use MARC::File::USMARC;
+
+use Data::Dumper;
+use File::Temp;
+use Getopt::Long qw(:DEFAULT GetOptionsFromArray);
+use Pod::Usage;
+use File::Spec;
+
+use OpenSRF::Utils::Logger qw/$logger/;
+use OpenSRF::AppSession;
+use OpenSRF::EX qw/:try/;
+use OpenSRF::Utils::SettingsClient;
+use OpenSRF::Utils::Cache;
+use OpenILS::Utils::Cronscript;
+use OpenILS::Utils::CStoreEditor;
+use OpenILS::Utils::Fieldmapper;
+require 'oils_header.pl';
+use vars qw/$apputils/;
+
+my $acq_ses;
+my $authtoken;
+my $conf;
+my $cache;
+my $editor;
+my $base_dir;
+my $share_dir;
+my $providers;
+my $debug = 0;
+
+my %defaults = (
+    'osrf-config=s' => '/openils/conf/opensrf_core.xml',
+    'user=s'        => 'admin',
+    'password=s'    => '',
+    'nodaemon'      => 0,
+    'poll-interval=i' => 10
+);
+
+# -----------------------------------------------------
+# Command-line args reading / munging
+# -----------------------------------------------------
+$OpenILS::Utils::Cronscript::debug=1 if $debug;
+$Getopt::Long::debug=1 if $debug > 1;
+my $o = OpenILS::Utils::Cronscript->new(\%defaults);
+
+my @script_args = ();
+
+if (grep {$_ eq '--'} @ARGV) {
+    print "Splitting options into groups\n" if $debug;
+    while (@ARGV) {
+        $_ = shift @ARGV;
+        $_ eq '--' and last;    # stop at the first --
+        push @script_args, $_;
+    }
+} else {
+    @script_args = @ARGV;
+    @ARGV = ();
+}
+
+print "Calling MyGetOptions ",
+    (@script_args ? "with options: " . join(' ', @script_args) : 'without options from command line'),
+    "\n" if $debug;
+
+my $real_opts = $o->MyGetOptions(\@script_args);
+$o->bootstrap;
+
+my $osrf_config   = $real_opts->{'osrf-config'};
+my $oils_username = $real_opts->{user};
+my $oils_password = $real_opts->{password};
+my $help          = $real_opts->{help};
+my $poll_interval = $real_opts->{'poll-interval'};
+   $debug         += $real_opts->{debug};
+
+foreach (keys %$real_opts) {
+    print("real_opt->{$_} = ", $real_opts->{$_}, "\n") if $real_opts->{debug} or $debug;
+}
+
+# FEEDBACK
+
+pod2usage(1) if $help;
+unless ($oils_password) {
+    print STDERR "\nERROR: password option required for session login\n\n";
+}
+
+$debug and print Dumper($o);
+
+if ($debug) {
+    foreach my $ref (qw/osrf_config oils_username oils_password help debug/) {
+        no strict 'refs';
+        printf "%16s => %s\n", $ref, (eval("\$$ref") || '');
+    }
+}
+
+$debug and print Dumper($real_opts);
+
+# -----------------------------------------------------
+# subs
+# -----------------------------------------------------
+
+# log in
+sub new_auth_token {
+    $authtoken = oils_login($oils_username, $oils_password, 'staff') 
+        or die "Unable to login to Evergreen as user $oils_username";
+    return $authtoken;
+}
+
+# log out
+sub clear_auth_token {
+    $apputils->simplereq(
+        'open-ils.auth',
+        'open-ils.auth.session.delete',
+        $authtoken
+    );
+}
+
+sub push_file_to_acq {
+    my $file = shift;
+    my $args = shift;
+
+    $logger->info("acq-or: pushing file '$file' to provider " . $args->{provider});
+
+    # Cache the file name like Vandelay does.  ACQ will 
+    # read contents of the cache and then delete them.
+    # The key can be any unique value.
+    my $key = $$ . time . rand();
+    $cache->put_cache("vandelay_import_spool_$key", {path => $file});
+
+    # some arguments are not optional
+    $args->{create_po} = 1;
+
+    # don't send our internal args to the service
+    my $local = delete $args->{_local};
+
+    my $req = $acq_ses->request(
+        'open-ils.acq.process_upload_records',
+        $authtoken,
+        $key, 
+        $args
+    );
+
+    while (my $resp = $req->recv(timeout => 600)) {
+        if(my $content = $resp->content) {
+            $debug and print Dumper($content);
+        } else {
+            warn "Request returned no data: " . Dumper($resp) . "\n";
+        }
+    }
+
+    # TODO: delete tmp queue?
+}
+
+my %org_cache;
+sub org_from_sn {
+    my $sn = shift;
+    return $org_cache{$sn} if $org_cache{$sn};
+    my $org = $editor->search_actor_org_unit({shortname => $sn})->[0];
+    if (!$org) {
+        warn "No such org unit in acq_order_reader config: '$sn'\n";
+        return undef;
+    }
+    return $org_cache{$sn} = $org;
+}
+
+# translate config info into a request arguments structure
+sub args_from_provider_conf {
+    my $conf = shift;
+    my %args;
+
+    my $pcode = $conf->{code};
+    my $orgsn = $conf->{owner};
+
+    $debug and print "Extracting request args for provider $pcode at $orgsn\n";
+
+    my $org = org_from_sn($conf->{owner}) or return undef;
+
+    my $provider = $editor->search_acq_provider({
+        code => $pcode,
+        owner => $org->id
+    })->[0];
+
+    if (!$provider) {
+        warn "No such provider in acq_order_reader config: '$pcode'\n";
+        return undef;
+    }
+
+    my $oa = org_from_sn($conf->{ordering_agency}) or return undef;
+
+    $args{provider} = $provider->id;
+    $args{ordering_agency} = $oa->id;
+    $args{activate_po} = ($conf->{activate_po} || '') =~ /true/i;
+    
+    # vandelay import options
+    my $vconf = $conf->{vandelay} || {};
+    $args{vandelay} = {};
+
+    # value options
+    for my $opt (
+        qw/
+            match_quality_ratio 
+            match_set 
+            bib_source 
+            merge_profile / ) {
+
+        $args{vandelay}->{$opt} = $vconf->{$opt} 
+    }
+
+    # bool options
+    for my $opt (
+        qw/
+            create_assets
+            import_no_match 
+            auto_overlay_exact 
+            auto_overlay_1match 
+            auto_overlay_best_match/ ) {
+
+        $args{vandelay}->{$opt} = 1 if ($vconf->{$opt} || '') =~ /true/i;
+    }
+
+    if ($vconf->{queue}) {
+        $args{vandelay}->{queue_name} = $vconf->{queue};
+        $args{vandelay}->{existing_queue} = $vconf->{queue};
+
+    } else {
+
+        # create a temporary queue
+        $args{vandelay}->{queue_name} = sprintf("acq-order-reader-%s-%s-%s", 
+            $org->shortname, $provider->code, $apputils->epoch2ISO8601(time));
+    }
+
+    $args{_local} = {
+        provider_code => $pcode, # good for debugging
+        dirname => File::Spec->catfile($base_dir, $conf->{subdir})
+    };
+
+    return \%args;
+}
+
+# returns the list of new order record files that
+# need to be processed for this vendor
+sub check_provider_files {
+    my $args = shift;
+    my $dirname = $args->{_local}->{dirname};
+    my $dh;
+    my @files;
+
+    $debug and print "Searching for new files at $dirname\n";
+
+    if ( !opendir($dh, $dirname) ) {
+        warn "Couldn't open dir '$dirname': $!";
+        return @files;
+    }
+
+    @files = readdir $dh;
+    # ignore '.', '..', and hidden files
+    @files = grep {$_ !~ /^\./} @files;
+
+    $logger->info("acq-or: found " . scalar(@files) . " ACQ order files at $dirname");
+
+    # return the file names w/ full path
+    return map {File::Spec->catfile($dirname, $_)} @files;
+}
+
+# -----------------------------------------------------
+# Main script
+# -----------------------------------------------------
+
+osrf_connect($osrf_config);
+
+$conf = OpenSRF::Utils::SettingsClient->new;
+$cache = OpenSRF::Utils::Cache->new;
+$editor = OpenILS::Utils::CStoreEditor->new;
+$acq_ses = OpenSRF::AppSession->create('open-ils.acq');
+
+my $user = $editor->search_actor_user({usrname => $oils_username})->[0];
+if (!$user) {
+    warn "Invalid user: $oils_username\n";
+    exit;
+}
+
+# read configs
+$base_dir = $conf->config_value(acq_order_reader => 'base_dir');
+$share_dir = $conf->config_value(acq_order_reader => 'shared_subdir');
+$providers = $conf->config_value(acq_order_reader => 'provider');
+$providers = [$providers] unless ref $providers eq 'ARRAY';
+
+$debug and print Dumper($providers);
+
+# -----------------------------------------------------
+# main loop
+# for each provider directory, plus the shared directory, check
+# to see if there are any files pending.  For any files found, push
+# them up to the ACQ service, then delete the file
+while (1) {
+
+    new_auth_token();
+    my $processed = 0;
+
+    # explicit providers
+    for my $provider_conf (@$providers) {
+        my $args = args_from_provider_conf($provider_conf) or next;
+        my @files = check_provider_files($args);
+        push_file_to_acq($_, $args) for @files;
+        $processed += scalar(@files);
+    }
+    
+    # shared directory
+    # TODO
+
+    clear_auth_token();
+
+    $logger->info("acq-or: loop processed $processed files");
+
+    $SIG{INT} = sub { 
+        print "Cleaning up...\n";
+        exit; # allows lockfile cleanup
+    };
+
+    # processing takes time.  If we processed any records
+    # during the current iteration, immediately check again
+    # for more work.  Otherwise, wait $poll_interval seconds
+    sleep $poll_interval if $processed == 0;
+}
+
+__END__
+
+=head1 NAME
+
+acq_order_reader.pl - Collect MARC order record files and pass them onto the ACQ service
+
+=head1 SYNOPSIS
+
+./acq_order_reader.pl [script opts ...]
+
+This script uses the EG common options from B<Cronscript>.  See --help output for those.
+
+Run C<perldoc marc_stream_importer.pl> for full documentation.
+
+Note: this script has to be run in the same directory as B<oils_header.pl>.
+
+Typical server-style execution will include a trailing C<&> to run in the background.
+
+=head1 OPTIONS
+
+The only required option is --password
+
+ --password         =<eg_password>
+ --user             =<eg_username>  default: admin
+ --nodaemon                         default: OFF       When used with --spoolfile, turns off Net::Server mode and runs this utility in the foreground
+
+
+=head2 Old style: --noqueue and associated options
+
+=head1 EXAMPLES
+
+./acq_order_reader.pl --user admin --password demo123
+
+./acq_order_reader.pl --user admin --password demo123 -poll-interval 3 --debug --nodaemon
+
+=head1 AUTHORS
+
+    Bill Erickson <erickson at esilibrary.com>
+    Code liberally copied from marc_stream_importer.pl
+
+=cut

-----------------------------------------------------------------------

Summary of changes:
 Open-ILS/examples/opensrf.xml.example            |   48 +++
 Open-ILS/src/support-scripts/acq_order_reader.pl |  381 ++++++++++++++++++++++
 2 files changed, 429 insertions(+), 0 deletions(-)
 create mode 100755 Open-ILS/src/support-scripts/acq_order_reader.pl


hooks/post-receive
-- 
Evergreen ILS


More information about the open-ils-commits mailing list