sip2 schema updates; more data
authorBill Erickson <berickxx@gmail.com>
Fri, 11 Sep 2020 19:37:39 +0000 (15:37 -0400)
committerBill Erickson <berickxx@gmail.com>
Wed, 28 Oct 2020 18:57:39 +0000 (14:57 -0400)
Signed-off-by: Bill Erickson <berickxx@gmail.com>
Open-ILS/examples/fm_IDL.xml
Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Item.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/SIP2/Session.pm
Open-ILS/src/sql/Pg/upgrade/XXXX.schema.sip-config.sql

index 72fb487..71a13e6 100644 (file)
@@ -13435,22 +13435,18 @@ SELECT  usr,
                </permacrud>
        </class>
 
-       <class id="csa" controller="open-ils.cstore open-ils.pcrud"
-               oils_obj:fieldmapper="config::sip_account
-               oils_persist:tablename="config.sip_account
-               reporter:label="SIP AccountS">
-               <fields oils_persist:primary="id" oils_persist:sequence="config.sip_account_id_seq">
+       <class id="cssg" controller="open-ils.cstore open-ils.pcrud"
+               oils_obj:fieldmapper="config::sip_setting_group
+               oils_persist:tablename="config.sip_setting_group
+               reporter:label="SIP Settings Group">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.sip_setting_id_seq">
                        <field name="id" reporter:datatype="id" reporter:label="ID" reporter:selector="sip_username"/>
-                       <field name="enabled" reporter:datatype="bool" reporter:label="Enabled"/>
-                       <field name="institution" reporter:datatype="text" reporter:label="Institution" oils_obj:required="true"/>
-                       <field name="sip_username" reporter:datatype="text" reporter:label="SIP Username" oils_obj:required="true"/>
-                       <field name="usr" reporter:datatype="link" reporter:label="ILS User" oils_obj:required="true"/>
-                       <field name="workstation" reporter:datatype="link" reporter:label="Workstation"/>
-                       <field name="av_format" reporter:datatype="text" reporter:label="SIP AV Format"/>
+                       <field name="label" reporter:datatype="text" reporter:label="Label" oils_obj:required="true"/>
+                       <field name="institution" reporter:datatype="text" reporter:label="SIP Institution" oils_obj:required="true"/>
+                       <field name="settings" reporter:datatype="link" reporter:label="Settings" oils_persist:virtual="true"/>
                </fields>
                <links>
-                       <link field="usr" reltype="has_a" key="id" map="" class="au"/>
-                       <link field="workstation" reltype="has_a" key="id" map="" class="aws"/>
+                       <link field="settings" reltype="has_many" key="setting_group" map="" class="css"/>
                </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                        <actions>
@@ -13461,17 +13457,20 @@ SELECT  usr,
                        </actions>
                </permacrud>
        </class>
-
        <class id="css" controller="open-ils.cstore open-ils.pcrud"
                oils_obj:fieldmapper="config::sip_setting" 
                oils_persist:tablename="config.sip_setting" 
                reporter:label="SIP Settings">
                <fields oils_persist:primary="id" oils_persist:sequence="config.sip_setting_id_seq">
                        <field name="id" reporter:datatype="id" reporter:label="ID" reporter:selector="sip_username"/>
-                       <field name="institution" reporter:datatype="text" reporter:label="Institution" oils_obj:required="true"/>
-                       <field name="name" reporter:datatype="text" reporter:label="Institution" oils_obj:required="true"/>
-                       <field name="value" reporter:datatype="text" reporter:label="Institution" oils_obj:required="true"/>
+                       <field name="setting_group" reporter:datatype="link" reporter:label="Settings Group" oils_obj:required="true"/>
+                       <field name="name" reporter:datatype="text" reporter:label="Name" oils_obj:required="true"/>
+                       <field name="description" reporter:datatype="text" reporter:label="Description" oils_obj:required="true"/>
+                       <field name="value" reporter:datatype="text" reporter:label="Value" oils_obj:required="true"/>
                </fields>
+               <links>
+                       <link field="setting_group" reltype="has_a" key="id" map="" class="cssg"/>
+               </links>
                <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
                        <actions>
                                <create permission="SIP_ADMIN" global_required="true"/>
@@ -13481,6 +13480,35 @@ SELECT  usr,
                        </actions>
                </permacrud>
        </class>
+       <class id="csa" controller="open-ils.cstore open-ils.pcrud"
+               oils_obj:fieldmapper="config::sip_account" 
+               oils_persist:tablename="config.sip_account" 
+               reporter:label="SIP AccountS">
+               <fields oils_persist:primary="id" oils_persist:sequence="config.sip_account_id_seq">
+                       <field name="id" reporter:datatype="id" reporter:label="ID" reporter:selector="sip_username"/>
+                       <field name="enabled" reporter:datatype="bool" reporter:label="Enabled"/>
+                       <field name="setting_group" reporter:datatype="link" reporter:label="Settings Group" oils_obj:required="true"/>
+                       <field name="sip_username" reporter:datatype="text" reporter:label="SIP Username" oils_obj:required="true"/>
+                       <field name="usr" reporter:datatype="link" reporter:label="ILS User" oils_obj:required="true"/>
+                       <field name="workstation" reporter:datatype="link" reporter:label="Workstation"/>
+                       <field name="av_format" reporter:datatype="text" reporter:label="SIP AV Format"/>
+               </fields>
+               <links>
+                       <link field="usr" reltype="has_a" key="id" map="" class="au"/>
+                       <link field="workstation" reltype="has_a" key="id" map="" class="aws"/>
+                       <link field="setting_group" reltype="has_a" key="id" map="" class="cssg"/>
+               </links>
+               <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1">
+                       <actions>
+                               <create permission="SIP_ADMIN" global_required="true"/>
+                               <retrieve permission="SIP_ADMIN" global_required="true"/>
+                               <update permission="SIP_ADMIN" global_required="true"/>
+                               <delete permission="SIP_ADMIN" global_required="true"/>
+                       </actions>
+               </permacrud>
+       </class>
+
+
 
        <!-- ********************************************************************************************************************* -->
 </IDL>
index 0c55354..72ab594 100644 (file)
@@ -146,11 +146,11 @@ sub handle_item_info {
     my $barcode = $SC->get_field_value($message, 'AB');
     my $config = $session->config;
 
-    my $idetails = OpenILS::Application::SIP2::Item->get_item_details(
+    my $details = OpenILS::Application::SIP2::Item->get_item_details(
         $session, barcode => $barcode
     );
 
-    if (!$idetails) {
+    if (!$details) {
         # No matching item found, return a minimal response.
         return {
             code => '18',
@@ -167,23 +167,24 @@ sub handle_item_info {
     return {
         code => '18',
         fixed_fields => [
-            $idetails->{circ_status},
+            $details->{circ_status},
             '02', # Security Marker, consistent with ../SIP*
-            $idetails->{fee_type},
+            $details->{fee_type},
             $SC->sipdate
         ],
         fields => [
             {AB => $barcode},
-            {AH => $idetails->{due_date}},
-            {AJ => $idetails->{title}},
-            {AP => $idetails->{item}->circ_lib->shortname},
-            {AQ => $idetails->{item}->circ_lib->shortname},
-            {BG => $idetails->{item}->circ_lib->shortname},
+            {AH => $details->{due_date}},
+            {AJ => $details->{title}},
+            {AP => $details->{item}->circ_lib->shortname},
+            {AQ => $details->{item}->circ_lib->shortname},
+            {BG => $details->{item}->circ_lib->shortname},
             {BH => $config->{settings}->{currency}},
-            {BV => $idetails->{item}->deposit_amount},
-            {CF => $idetails->{hold_queue_length}},
-            {CK => $idetails->{media_type}},
-            {CM => $idetails->{hold_pickup_date}}
+            {BV => $details->{item}->deposit_amount},
+            {CF => $details->{hold_queue_length}},
+            {CK => $details->{media_type}},
+            {CM => $details->{hold_pickup_date}},
+            {CY => $details->{hold_patron_barcode}}
         ]
     };
 }
@@ -200,7 +201,7 @@ sub handle_patron_info {
 
     my $list_items = $SC->patron_summary_list_items($summary);
 
-    my $pdetails = OpenILS::Application::SIP2::Patron->get_patron_details(
+    my $details = OpenILS::Application::SIP2::Patron->get_patron_details(
         $session,
         barcode => $barcode,
         password => $password,
@@ -210,21 +211,21 @@ sub handle_patron_info {
     );
 
     my $response = 
-        patron_response_common_data($session, $barcode, $password, $pdetails);
+        patron_response_common_data($session, $barcode, $password, $details);
 
     $response->{code} = '64';
 
-    return $response unless $pdetails;
-    my $patron = $pdetails->{patron};
+    return $response unless $details;
+    my $patron = $details->{patron};
 
     push(
         @{$response->{fixed_fields}}, 
-        $SC->count4($pdetails->{holds_count}),
-        $SC->count4($pdetails->{overdue_count}),
-        $SC->count4($pdetails->{out_count}),
-        $SC->count4($pdetails->{fine_count}),
-        $SC->count4($pdetails->{recall_count}),
-        $SC->count4($pdetails->{unavail_holds_count})
+        $SC->count4($details->{holds_count}),
+        $SC->count4($details->{overdue_count}),
+        $SC->count4($details->{out_count}),
+        $SC->count4($details->{fine_count}),
+        $SC->count4($details->{recall_count}),
+        $SC->count4($details->{unavail_holds_count})
     );
 
     push(
@@ -237,23 +238,23 @@ sub handle_patron_info {
     );
 
     if ($list_items eq 'hold_items') {
-        for my $hold (@{$pdetails->{hold_items}}) {
+        for my $hold (@{$details->{hold_items}}) {
             push(@{$response->{fields}}, {AS => $hold});
         }
     } elsif ($list_items eq 'charged_items') {
-        for my $item (@{$pdetails->{items_out}}) {
+        for my $item (@{$details->{items_out}}) {
             push(@{$response->{fields}}, {AU => $item});
         }
     } elsif ($list_items eq 'overdue_items') {
-        for my $item (@{$pdetails->{overdue_items}}) {
+        for my $item (@{$details->{overdue_items}}) {
             push(@{$response->{fields}}, {AT => $item});
         }
     } elsif ($list_items eq 'fine_items') {
-        for my $item (@{$pdetails->{fine_items}}) {
+        for my $item (@{$details->{fine_items}}) {
             push(@{$response->{fields}}, {AV => $item});
         }
     } elsif ($list_items eq 'unavailable_holds') {
-        for my $item (@{$pdetails->{unavailable_holds}}) {
+        for my $item (@{$details->{unavailable_holds}}) {
             push(@{$response->{fields}}, {CD => $item});
         }
     }
@@ -270,14 +271,14 @@ sub handle_patron_status {
     my $barcode = $SC->get_field_value($message, 'AA');
     my $password = $SC->get_field_value($message, 'AD');
 
-    my $pdetails = OpenILS::Application::SIP2::Patron->get_patron_details(
+    my $details = OpenILS::Application::SIP2::Patron->get_patron_details(
         $session,
         barcode => $barcode,
         password => $password
     );
 
     my $response = patron_response_common_data(
-        $session, $barcode, $password, $pdetails);
+        $session, $barcode, $password, $details);
 
     $response->{code} = '24';
 
@@ -289,9 +290,9 @@ sub handle_patron_status {
 # Note we don't call Patron->get_patron_details here since different
 # messages collect different amounts of data.
 sub patron_response_common_data {
-    my ($session, $barcode, $password, $pdetails) = @_;
+    my ($session, $barcode, $password, $details) = @_;
 
-    if (!$pdetails) {
+    if (!$details) {
         # No such user.  Return a stub response with all things denied.
 
         return {
@@ -313,33 +314,34 @@ sub patron_response_common_data {
         };
     }
 
-    my $patron = $pdetails->{patron};
+    my $patron = $details->{patron};
  
     return {
         fixed_fields => [
-            $SC->spacebool($pdetails->{charge_denied}),
-            $SC->spacebool($pdetails->{renew_denied}),
-            $SC->spacebool($pdetails->{recall_denied}),
-            $SC->spacebool($pdetails->{holds_denied}),
+            $SC->spacebool($details->{charge_denied}),
+            $SC->spacebool($details->{renew_denied}),
+            $SC->spacebool($details->{recall_denied}),
+            $SC->spacebool($details->{holds_denied}),
             $SC->spacebool($patron->card->active eq 'f'),
             $SC->spacebool(0), # too many charged
-            $SC->spacebool($pdetails->{too_may_overdue}),
+            $SC->spacebool($details->{too_may_overdue}),
             $SC->spacebool(0), # too many renewals
             $SC->spacebool(0), # too many claims retruned
             $SC->spacebool(0), # too many lost
-            $SC->spacebool($pdetails->{too_many_fines}),
-            $SC->spacebool($pdetails->{too_many_fines}),
+            $SC->spacebool($details->{too_many_fines}),
+            $SC->spacebool($details->{too_many_fines}),
             $SC->spacebool(0), # recall overdue
-            $SC->spacebool($pdetails->{too_many_fines}),
+            $SC->spacebool($details->{too_many_fines}),
             '000', # language
             $SC->sipdate
         ],
         fields => [
-            {AO => $session->config->{institution}},
             {AA => $barcode},
-            {BL => $SC->sipbool(1)},           # valid patron
-            {BV => $pdetails->{balance_owed}}, # fee amount
-            {CQ => $SC->sipbool($password)}    # password verified if exists
+            {AO => $session->config->{institution}},
+            {BH => $session->config->{settings}->{currency}},
+            {BL => $SC->sipbool(1)},          # valid patron
+            {BV => $details->{balance_owed}}, # fee amount
+            {CQ => $SC->sipbool($password)}   # password verified if exists
         ]
     };
 }
index 1def432..ba25247 100644 (file)
@@ -90,10 +90,13 @@ sub get_item_details {
     }
 
 
-    if ($details->{hold}) {
-        my $pickup_date = $details->{hold}->shelf_expire_time;
+    if (my $hold = $details->{hold}) {
+        my $pickup_date = $hold->shelf_expire_time;
         $details->{hold_pickup_date} =
             $pickup_date ? $SC->sipdate($pickup_date) : undef;
+
+        my $card = $e->search_actor_card({usr => $hold->usr})->[0];
+        $details->{hold_patron_barcode} = $card->barcode if $card;
     }
 
     my ($title_entry) = grep {$_->name eq 'title'}
index 097b84b..2137d1b 100644 (file)
@@ -13,7 +13,7 @@ $json->allow_nonref(1);
 # Supported Messages (BX)
 # Currently hard-coded, since it's based on availabilty of functionality
 # in the code, but it could be moved into the database to limit access for 
-# specific institutions.
+# specific setting groups.
 use constant INSTITUTION_SUPPORTS => [ 
     'Y', # patron status request,
     'Y', # checkout,
@@ -49,35 +49,21 @@ sub config {
     my $self = shift;
     return $self->{config} if $self->{config};
 
-    my $inst = $self->sip_account->institution;
+    my $group = $self->editor->retrieve_config_sip_setting_group([
+        $self->sip_account->setting_group,
+        {flesh => 1, flesh_fields => {cssg => ['settings']}}
+    ]);
 
     my $config = {
-        institution => $inst,
-        settings => {
-            currency => 'USD' # TODO add db setting
-        },
+        institution => $group->institution,
         supports => INSTITUTION_SUPPORTS
     };
 
-    # Institution "*" provides default values for all institution configs.
-    my $settings = 
-        $self->editor->search_config_sip_setting({institution => ['*', $inst]});
-
-    # Institution specific settings.
-    for my $set (grep {$_->institution eq $inst} @$settings) {
-        $config->{settings}->{$set->name} = $json->decode($set->value);
-    }
-
-    # Apply values for global settings without replacing 
-    # institution-specific values.
-    for my $set (grep {$_->institution eq '*'} @$settings) {
-        my $name = $set->name;
-        my $value = $json->decode($set->value);
-
-        $config->{settings}->{$name} = $value 
-            unless exists $config->{settings}->{$name};
-    }
+    # Decode and hashify settings for easy access
+    $config->{settings} = 
+        {map {$_->name => $json->decode($_->value)} @{$group->settings}};
 
+    $logger->info("SIP settings " . $json->encode($config->{settings}));
     return $self->{config} = $config;
 }
 
index 3b6c121..f019bf9 100644 (file)
@@ -3,10 +3,29 @@ BEGIN;
 
 -- SELECT evergreen.upgrade_deps_block_check('TODO', :eg_version);
 
+-- Collections of settings that can be linked to one or more SIP accounts.
+CREATE TABLE config.sip_setting_group (
+    id SERIAL   PRIMARY KEY,
+    label       TEXT UNIQUE NOT NULL,
+    institution TEXT NOT NULL -- Duplicates OK
+);
+
+-- Key/value setting pairs
+CREATE TABLE config.sip_setting (
+    id SERIAL       PRIMARY KEY,
+    setting_group   INTEGER NOT NULL REFERENCES config.sip_setting_group (id)
+                    ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED,
+    name            TEXT NOT NULL,
+    description     TEXT NOT NULL,
+    value           JSON NOT NULL,
+    CONSTRAINT      name_once_per_inst UNIQUE (setting_group, name)
+);
+
 CREATE TABLE config.sip_account (
     id              SERIAL PRIMARY KEY,
     enabled         BOOLEAN NOT NULL DEFAULT TRUE,
-    institution     TEXT NOT NULL,
+    setting_group   INTEGER NOT NULL REFERENCES config.sip_setting_group (id)
+                    DEFERRABLE INITIALLY DEFERRED,
     sip_username    TEXT NOT NULL,
     sip_password    BIGINT NOT NULL REFERENCES actor.passwd 
                     DEFERRABLE INITIALLY DEFERRED,
@@ -16,43 +35,59 @@ CREATE TABLE config.sip_account (
     av_format       TEXT -- e.g. '3m'
 );
 
--- institution and global-level key/value setting pairs.
-CREATE TABLE config.sip_setting (
-    id SERIAL   PRIMARY KEY,
-    institution TEXT NOT NULL, -- '*' applies to all institutions
-    name        TEXT NOT NULL,
-    value       JSON NOT NULL,
-    CONSTRAINT  name_once_per_inst UNIQUE (institution, name)
-);
-
 -- SEED DATA
 
 INSERT INTO actor.passwd_type (code, name, login, crypt_algo, iter_count)
     VALUES ('sip2', 'SIP2 Client Password', FALSE, 'bf', 5);
 
-INSERT INTO config.sip_setting (institution, name, value)
-VALUES 
-    ('*', 'allow_sc_status_before_login', 'true'),
-    ('*', 'currency', '"USD"'),
-    ('*', 'due_date_use_sip_date_format', 'false'),
-    ('*', 'patron_status_permit_loans', 'false'),
-    ('*', 'patron_status_permit_all', 'false'), 
-    ('*', 'msg64_summary_datatype', '"title"'),
-    ('*', 'msg64_hold_items_available', '"title"')
-;
+INSERT INTO config.sip_setting_group (label, institution) 
+    VALUES ('Example Setting Group', 'example');
+
+INSERT INTO config.sip_setting (setting_group, description, name, value)
+VALUES (
+    (SELECT id FROM config.sip_setting_group WHERE institution = 'example'), 
+    'Monetary amounts are reported in this currency',
+    'currency', '"USD"'
+), (
+    (SELECT id FROM config.sip_setting_group WHERE institution = 'example'), 
+    'Allow clients to request the SIP server status before login (message 99)',
+    'allow_sc_status_before_login', 'true'
+), (
+    (SELECT id FROM config.sip_setting_group WHERE institution = 'example'), 
+    'Due date uses 18-char date format (YYYYMMDDZZZZHHMMSS).  Otherwise "YYYY-MM-DD HH:MM:SS',
+    'due_date_use_sip_date_format', 'false'
+), (
+    (SELECT id FROM config.sip_setting_group WHERE institution = 'example'), 
+    'Checkout and renewal are allowed even when penalties blocking these actions exist',
+    'patron_status_permit_loans', 'false'
+), (
+    (SELECT id FROM config.sip_setting_group WHERE institution = 'example'), 
+    'Holds, checkouts, and renewals allowed regardless of blocking penalties',
+    'patron_status_permit_all', 'false'
+), (
+    (SELECT id FROM config.sip_setting_group WHERE institution = 'example'), 
+    'Patron circulation data may be returned as either "title" or "barcode"',
+    'msg64_summary_datatype', '"title"'
+), (
+    (SELECT id FROM config.sip_setting_group WHERE institution = 'example'), 
+    'Patron holds data may be returned as either "title" or "barcode"',
+    'msg64_hold_items_available', '"title"'
+);
 
 
 /* EXAMPLE SETTINGS
 
+
 -- Example linking a SIP password to the 'admin' account.
 SELECT actor.set_passwd(1, 'sip2', 'sip_password');
 
 INSERT INTO actor.workstation (name, owning_lib) VALUES ('BR1-SIP2-Gateway', 4);
 
 INSERT INTO config.sip_account(
-    institution, sip_username, sip_password, usr, workstation, av_format
+    setting_group, sip_username, sip_password, usr, workstation, av_format
 ) VALUES (
-    'example', 'admin', 
+    (SELECT id FROM config.sip_setting_group WHERE institution = 'example'), 
+    'admin', 
     (SELECT id FROM actor.passwd WHERE usr = 1 AND passwd_type = 'sip2'),
     1, 
     (SELECT id FROM actor.workstation WHERE name = 'BR1-SIP2-Gateway'),