From 7891c0241e9b5273f122f8d77cb20ebbf2d5cf6b Mon Sep 17 00:00:00 2001 From: Bill Erickson <berickxx@gmail.com> Date: Tue, 13 Mar 2018 14:06:23 -0400 Subject: [PATCH] LP#1750894 Workstation & Cascade settings Adds a new config.workstation_setting_type table for managing workstation-specific settings. Adds new PG and perl API functionality for determining values for settings which may be represnted as workstation, user, and/or org unit settings. Teaches the AngularJS browser client to load and apply most settings at the server. Values for settings stored in localStorage/Hatch are migrated to server settings at time of next use. Stock workstation setting types added to accommodate most browser client settings. Signed-off-by: Bill Erickson <berickxx@gmail.com> Signed-off-by: Kathy Lussier <klussier@masslnc.org> --- Open-ILS/examples/fm_IDL.xml | 46 +- Open-ILS/src/perlmods/MANIFEST | 1 + .../src/perlmods/lib/OpenILS/Application/Actor.pm | 1 + .../lib/OpenILS/Application/Actor/Settings.pm | 306 +++++++ Open-ILS/src/sql/Pg/002.schema.config.sql | 58 ++ Open-ILS/src/sql/Pg/005.schema.actors.sql | 164 ++++ Open-ILS/src/sql/Pg/950.data.seed-values.sql | 894 ++++++++++++++++++++- .../upgrade/XXXX.schema.workstation-settings.sql | 227 ++++++ .../Pg/upgrade/YYYY.data.workstation-settings.sql | 893 ++++++++++++++++++++ .../templates/staff/admin/workstation/index.tt2 | 2 + .../js/ui/default/staff/admin/workstation/app.js | 4 +- .../web/js/ui/default/staff/circ/checkin/app.js | 13 +- Open-ILS/web/js/ui/default/staff/services/hatch.js | 386 ++++++++- 13 files changed, 2954 insertions(+), 41 deletions(-) create mode 100644 Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Settings.pm create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.schema.workstation-settings.sql create mode 100644 Open-ILS/src/sql/Pg/upgrade/YYYY.data.workstation-settings.sql diff --git a/Open-ILS/examples/fm_IDL.xml b/Open-ILS/examples/fm_IDL.xml index 5d7be8ceff..66d146e0d0 100644 --- a/Open-ILS/examples/fm_IDL.xml +++ b/Open-ILS/examples/fm_IDL.xml @@ -12499,8 +12499,52 @@ SELECT usr, </permacrud> </class> - <!-- ********************************************************************************************************************* --> + <class id="cwst" controller="open-ils.cstore open-ils.pcrud" + oils_obj:fieldmapper="config::workstation_setting_type" + oils_persist:tablename="config.workstation_setting_type" + reporter:label="Workstation Setting Type"> + <fields oils_persist:primary="name"> + <field name="name" reporter:datatype="text"/> + <field name="label" reporter:datatype="text" oils_persist:i18n="true"/> + <field name="description" reporter:datatype="text" oils_persist:i18n="true"/> + <field name="datatype" reporter:datatype="text"/> + <field name="fm_class" reporter:datatype="text"/> + <field name="grp" reporter:datatype="link"/> + </fields> + <links> + <link field="name" reltype="has_many" key="name" map="" class="aous"/> + <link field="grp" reltype="has_a" key="name" map="" class="csg"/> + </links> + <permacrud xmlns="http://open-ils.org/spec/opensrf/IDL/permacrud/v1"> + <actions> + <create permission="ADMIN_WORKSTATION_SETTING_TYPE" global_required="true"/> + <retrieve/> + <update permission="ADMIN_WORKSTATION_SETTING_TYPE" global_required="true"/> + <delete permission="ADMIN_WORKSTATION_SETTING_TYPE" global_required="true"/> + </actions> + </permacrud> + </class> + <!-- no pcrud access is granted for now, because it's assumed these + setting values will be applied and retrived via the API. --> + <class id="awss" + controller="open-ils.cstore" + oils_obj:fieldmapper="actor::workstation_setting" + oils_persist:tablename="actor.workstation_setting" + reporter:label="Workstation Setting"> + <fields oils_persist:primary="id" oils_persist:sequence="actor.workstation_setting_id_seq"> + <field reporter:label="Setting ID" name="id" reporter:datatype="id" /> + <field reporter:label="Name" name="name" reporter:datatype="link"/> + <field reporter:label="Value" name="value" reporter:datatype="text"/> + <field reporter:label="Workstation" name="workstation" reporter:datatype="link"/> + </fields> + <links> + <link field="name" reltype="has_a" key="name" map="" class="cwst"/> + <link field="workstation" reltype="has_a" key="id" map="" class="aws"/> + </links> + </class> + + <!-- ********************************************************************************************************************* --> </IDL> <!-- diff --git a/Open-ILS/src/perlmods/MANIFEST b/Open-ILS/src/perlmods/MANIFEST index cb980a6e22..8473b19ce2 100644 --- a/Open-ILS/src/perlmods/MANIFEST +++ b/Open-ILS/src/perlmods/MANIFEST @@ -18,6 +18,7 @@ lib/OpenILS/Application/Actor/Container.pm lib/OpenILS/Application/Actor/Friends.pm lib/OpenILS/Application/Actor/Stage.pm lib/OpenILS/Application/Actor/UserGroups.pm +lib/OpenILS/Application/Actor/Settings.pm lib/OpenILS/Application/AppUtils.pm lib/OpenILS/Application/Booking.pm lib/OpenILS/Application/Cat.pm diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm index e62982e79f..503cf35a44 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm @@ -31,6 +31,7 @@ use OpenILS::Application::Actor::ClosedDates; use OpenILS::Application::Actor::UserGroups; use OpenILS::Application::Actor::Friends; use OpenILS::Application::Actor::Stage; +use OpenILS::Application::Actor::Settings; use OpenILS::Utils::CStoreEditor qw/:funcs/; use OpenILS::Utils::Penalty; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Settings.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Settings.pm new file mode 100644 index 0000000000..29d5e9ed49 --- /dev/null +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/Actor/Settings.pm @@ -0,0 +1,306 @@ +package OpenILS::Application::Actor::Settings; +use strict; use warnings; +use base 'OpenILS::Application'; +use OpenSRF::AppSession; +use OpenSRF::Utils::Logger q/$logger/; +use OpenILS::Application::AppUtils; +use OpenILS::Utils::CStoreEditor q/:funcs/; +use OpenILS::Utils::Fieldmapper; +use OpenSRF::Utils::JSON; +use OpenILS::Event; +my $U = "OpenILS::Application::AppUtils"; + +# Setting names may only contains letters, numbers, unders, and dots. +my $name_regex = qr/[^a-zA-Z0-9_\.]/; + +__PACKAGE__->register_method ( + method => 'retrieve_settings', + api_name => 'open-ils.actor.settings.retrieve', + stream => 1, + signature => { + desc => q/ + Returns org unit, user, and workstation setting values + for the requested setting types. + + The API makes a best effort to find the correct setting + value based on the available context data. + + If no auth token is provided, only publicly visible org + unit settings may be returned. + + If no workstation is linked to the provided auth token, only + user settings and perm-visible org unit settings may be + returned. + + If no org unit is provided, but a workstation is linked to the + auth token, the owning lib of the workstation is used as the + context org unit. + /, + params => [ + {desc => 'settings. List of setting names', type => 'array'}, + {desc => 'authtoken. Optional', type => 'string'}, + {desc => 'org_id. Optional', type => 'number'} + ], + return => { + desc => q/ + Stream of setting name=>value pairs in the same order + as the provided list of setting names. No key-value + pair is returned for settings that have no value defined./, + type => 'string' + } + } +); + +sub retrieve_settings { + my ($self, $client, $settings, $auth, $org_id) = @_; + + my ($aou_id, $user_id, $ws_id, $evt) = get_context($auth, $org_id); + return $evt if $evt; # bogus auth token + + return OpenILS::Event->new('BAD_PARAMS', + desc => 'Cannot retrieve settings without a user or org unit') + unless ($user_id || $aou_id); + + # Setting names may only contains letters, numbers, unders, and dots. + s/$name_regex//g foreach @$settings; + + # Encode as a db-friendly array. + my $settings_str = '{' . join(',', @$settings) . '}'; + + # Some settings could be bulky, so fetch them as a stream from + # cstore, relaying values back to the caller as they arrive. + my $ses = OpenSRF::AppSession->create('open-ils.cstore'); + my $req = $ses->request('open-ils.cstore.json_query', { + from => [ + 'actor.get_cascade_setting_batch', + $settings_str, $aou_id, $user_id, $ws_id + ] + }); + + while (my $resp = $req->recv) { + my $summary = $resp->content; + $summary->{value} = OpenSRF::Utils::JSON->JSON2perl($summary->{value}); + $client->respond($summary); + } + + $ses->kill_me; + return undef; +} + +# Returns ($org_id, $user_id, $ws_id, $evt); +# Any value may be undef. +sub get_context { + my ($auth, $org_id) = @_; + + return ($org_id) unless $auth; + + my $e = new_editor(authtoken => $auth); + return (undef, undef, undef, $e->event) unless $e->checkauth; + + my $user_id = $e->requestor->id; + my $ws_id = $e->requestor->wsid; + + # default to the workstation org if needed. + $org_id = $e->requestor->ws_ou if $ws_id && !$org_id; + + return ($org_id, $user_id, $ws_id); +} + +__PACKAGE__->register_method ( + method => 'apply_user_or_ws_setting', + api_name => 'open-ils.actor.settings.apply.user_or_ws', + stream => 1, + signature => { + desc => q/ + Apply values to user or workstation settings, depending + on which is supported via local configuration. + + The API ignores nonexistent settings and only returns error + events when an auth, permission, or internal error occurs. + /, + params => [ + {desc => 'authtoken', type => 'string'}, + {desc => 'settings. Hash of key/value pairs', type => 'object'}, + ], + return => { + desc => 'Returns the number of applied settings on succes, Event on error.', + type => 'number or event' + } + } +); + +sub apply_user_or_ws_setting { + my ($self, $client, $auth, $settings) = @_; + + my $e = new_editor(authtoken => $auth, xact => 1); + return $e->die_event unless $e->checkauth; + + my $applied = 0; + my $ws_allowed = 0; + + for my $name (keys %$settings) { + $name =~ s/$name_regex//g; + my $val = $$settings{$name}; + my $stype = $e->retrieve_config_usr_setting_type($name); + + if ($stype) { + my $evt = apply_user_setting($e, $name, $val); + return $evt if $evt; + $applied++; + + } elsif ($e->requestor->wsid) { + $stype = $e->retrieve_config_workstation_setting_type($name); + next unless $stype; # no such workstation setting, skip. + + if (!$ws_allowed) { + # Confirm the caller has permission to apply workstation + # settings at the logged-in workstation before applying. + # Do the perm check here so it's only needed once per batch. + return $e->die_event unless + $ws_allowed = $e->allowed('APPLY_WORKSTATION_SETTING'); + } + + my $evt = apply_workstation_setting($e, $name, $val); + return $evt if $evt; + $applied++; + } + } + + $e->commit if $applied > 0; + $e->rollback if $applied == 0; + + return $applied; +} + +# CUD for user settings. +# Returns undef on success, Event on error. +# NOTE: This code was copied as-is from +# open-ils.actor.patron.settings.update, because it lets us +# manage the batch of updates within a single transaction. Also +# worth noting the APIs in this mod could eventually replace +# open-ils.actor.patron.settings.update. Maybe. +sub apply_user_setting { + my ($e, $name, $val) = @_; + my $user_id = $e->requestor->id; + + my $set = $e->search_actor_user_setting( + {usr => $user_id, name => $name})->[0]; + + if (defined $val) { + $val = OpenSRF::Utils::JSON->perl2JSON($val); + if ($set) { + $set->value($val); + $e->update_actor_user_setting($set) or return $e->die_event; + } else { + $set = Fieldmapper::actor::user_setting->new; + $set->usr($user_id); + $set->name($name); + $set->value($val); + $e->create_actor_user_setting($set) or return $e->die_event; + } + } elsif ($set) { + $e->delete_actor_user_setting($set) or return $e->die_event; + } + + return undef; +} + +# CUD for workstation settings. +# Assumes ->wsid contains a value and permissions have been checked. +# Returns undef on success, Event on error. +sub apply_workstation_setting { + my ($e, $name, $val) = @_; + my $ws_id = $e->requestor->wsid; + + my $set = $e->search_actor_workstation_setting( + {workstation => $ws_id, name => $name})->[0]; + + if (defined $val) { + $val = OpenSRF::Utils::JSON->perl2JSON($val); + + if ($set) { + $set->value($val); + $e->update_actor_workstation_setting($set) or return $e->die_event; + } else { + $set = Fieldmapper::actor::workstation_setting->new; + $set->workstation($ws_id); + $set->name($name); + $set->value($val); + $e->create_actor_workstation_setting($set) or return $e->die_event; + } + } elsif ($set) { + $e->delete_actor_workstation_setting($set) or return $e->die_event; + } + + return undef; +} + +__PACKAGE__->register_method ( + method => 'applied_settings', + api_name => 'open-ils.actor.settings.staff.applied.names', + stream => 1, + authoritative => 1, + signature => { + desc => q/ + Returns a list of setting names where a value is applied to + the current user or workstation. + + This is a staff-only API created primarily to support the + getKeys() functionality used in the browser client for + server-managed settings. + /, + params => [ + {desc => 'authtoken', type => 'string'}, + {desc => + 'prefix. Limit keys to those starting with $prefix', + type => 'string' + }, + ], + return => { + desc => 'List of strings, Event on error', + type => 'array' + } + } +); + +sub applied_settings { + my ($self, $client, $auth, $prefix) = @_; + + my $e = new_editor(authtoken => $auth); + return $e->event unless $e->checkauth; + return $e->event unless $e->allowed('STAFF_LOGIN'); + + my $query = { + select => {awss => ['name']}, + from => 'awss', + where => { + workstation => $e->requestor->wsid + } + }; + + $query->{where}->{name} = {like => "$prefix%"} if $prefix; + + for my $key (@{$e->json_query($query)}) { + $client->respond($key->{name}); + } + + $query = { + select => {aus => ['name']}, + from => 'aus', + where => { + usr => $e->requestor->id + } + }; + + $query->{where}->{name} = {like => "$prefix%"} if $prefix; + + for my $key (@{$e->json_query($query)}) { + $client->respond($key->{name}); + } + + return undef; +} + + + +1; diff --git a/Open-ILS/src/sql/Pg/002.schema.config.sql b/Open-ILS/src/sql/Pg/002.schema.config.sql index b44ebbd3a5..31cd398275 100644 --- a/Open-ILS/src/sql/Pg/002.schema.config.sql +++ b/Open-ILS/src/sql/Pg/002.schema.config.sql @@ -683,6 +683,64 @@ CREATE TABLE config.usr_setting_type ( ); +CREATE TABLE config.workstation_setting_type ( + name TEXT PRIMARY KEY, + label TEXT UNIQUE NOT NULL, + grp TEXT REFERENCES config.settings_group (name), + description TEXT, + datatype TEXT NOT NULL DEFAULT 'string', + fm_class TEXT, + -- + -- define valid datatypes + -- + CONSTRAINT cwst_valid_datatype CHECK ( datatype IN + ( 'bool', 'integer', 'float', 'currency', 'interval', + 'date', 'string', 'object', 'array', 'link' ) ), + -- + -- fm_class is meaningful only for 'link' datatype + -- + CONSTRAINT cwst_no_empty_link CHECK + ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR + ( datatype <> 'link' AND fm_class IS NULL ) ) +); + +-- Prevent setting types from being both user and workstation settings. +CREATE OR REPLACE FUNCTION config.setting_is_user_or_ws() +RETURNS TRIGGER AS $FUNC$ +BEGIN + + IF TG_TABLE_NAME = 'usr_setting_type' THEN + PERFORM TRUE FROM config.workstation_setting_type cwst + WHERE cwst.name = NEW.name; + IF NOT FOUND THEN + RETURN NULL; + END IF; + END IF; + + IF TG_TABLE_NAME = 'workstation_setting_type' THEN + PERFORM TRUE FROM config.usr_setting_type cust + WHERE cust.name = NEW.name; + IF NOT FOUND THEN + RETURN NULL; + END IF; + END IF; + + RAISE EXCEPTION + '% Cannot be used as both a user setting and a workstation setting.', + NEW.name; +END; +$FUNC$ LANGUAGE PLPGSQL STABLE; + +CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws + AFTER INSERT OR UPDATE ON config.usr_setting_type + FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws(); + +CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws + AFTER INSERT OR UPDATE ON config.workstation_setting_type + FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws(); + + + -- Some handy functions, based on existing ones, to provide optional ingest normalization CREATE OR REPLACE FUNCTION public.left_trunc( TEXT, INT ) RETURNS TEXT AS $func$ diff --git a/Open-ILS/src/sql/Pg/005.schema.actors.sql b/Open-ILS/src/sql/Pg/005.schema.actors.sql index d96f83dd50..1152d97616 100644 --- a/Open-ILS/src/sql/Pg/005.schema.actors.sql +++ b/Open-ILS/src/sql/Pg/005.schema.actors.sql @@ -1054,4 +1054,168 @@ BEGIN END LOOP; END $$ LANGUAGE PLPGSQL; +CREATE TABLE actor.workstation_setting ( + id SERIAL PRIMARY KEY, + workstation INT NOT NULL REFERENCES actor.workstation (id) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + name TEXT NOT NULL REFERENCES config.workstation_setting_type (name) + ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + value JSON NOT NULL +); + +CREATE INDEX actor_workstation_setting_workstation_idx + ON actor.workstation_setting (workstation); + +CREATE TYPE actor.cascade_setting_summary AS ( + name TEXT, + value JSON, + has_org_setting BOOLEAN, + has_user_setting BOOLEAN, + has_workstation_setting BOOLEAN +); + +CREATE OR REPLACE FUNCTION actor.get_cascade_setting( + setting_name TEXT, org_id INT, user_id INT, workstation_id INT) + RETURNS actor.cascade_setting_summary AS +$FUNC$ +DECLARE + setting_value JSON; + summary actor.cascade_setting_summary; + org_setting_type config.org_unit_setting_type%ROWTYPE; +BEGIN + + summary.name := setting_name; + + -- Collect the org setting type status first in case we exit early. + -- The existance of an org setting type is not considered + -- privileged information. + SELECT INTO org_setting_type * + FROM config.org_unit_setting_type WHERE name = setting_name; + IF FOUND THEN + summary.has_org_setting := TRUE; + ELSE + summary.has_org_setting := FALSE; + END IF; + + -- User and workstation settings have the same priority. + -- Start with user settings since that's the simplest code path. + -- The workstation_id is ignored if no user_id is provided. + IF user_id IS NOT NULL THEN + + SELECT INTO summary.value value FROM actor.usr_setting + WHERE usr = user_id AND name = setting_name; + + IF FOUND THEN + -- if we have a value, we have a setting type + summary.has_user_setting := TRUE; + + IF workstation_id IS NOT NULL THEN + -- Only inform the caller about the workstation + -- setting type disposition when a workstation id is + -- provided. Otherwise, it's NULL to indicate UNKNOWN. + summary.has_workstation_setting := FALSE; + END IF; + + RETURN summary; + END IF; + + -- no user setting value, but a setting type may exist + SELECT INTO summary.has_user_setting EXISTS ( + SELECT TRUE FROM config.usr_setting_type + WHERE name = setting_name + ); + + IF workstation_id IS NOT NULL THEN + + IF NOT summary.has_user_setting THEN + -- A workstation setting type may only exist when a user + -- setting type does not. + + SELECT INTO summary.value value + FROM actor.workstation_setting + WHERE workstation = workstation_id AND name = setting_name; + + IF FOUND THEN + -- if we have a value, we have a setting type + summary.has_workstation_setting := TRUE; + RETURN summary; + END IF; + + -- no value, but a setting type may exist + SELECT INTO summary.has_workstation_setting EXISTS ( + SELECT TRUE FROM config.workstation_setting_type + WHERE name = setting_name + ); + END IF; + + -- Finally make use of the workstation to determine the org + -- unit if none is provided. + IF org_id IS NULL AND summary.has_org_setting THEN + SELECT INTO org_id owning_lib + FROM actor.workstation WHERE id = workstation_id; + END IF; + END IF; + END IF; + + -- Some org unit settings are protected by a view permission. + -- First see if we have any data that needs protecting, then + -- check the permission if needed. + + IF NOT summary.has_org_setting THEN + RETURN summary; + END IF; + + -- avoid putting the value into the summary until we confirm + -- the value should be visible to the caller. + SELECT INTO setting_value value + FROM actor.org_unit_ancestor_setting(setting_name, org_id); + + IF NOT FOUND THEN + -- No value found -- perm check is irrelevant. + RETURN summary; + END IF; + + IF org_setting_type.view_perm IS NOT NULL THEN + + IF user_id IS NULL THEN + RAISE NOTICE 'Perm check required but no user_id provided'; + RETURN summary; + END IF; + + IF NOT permission.usr_has_perm( + user_id, (SELECT code FROM permission.perm_list + WHERE id = org_setting_type.view_perm), org_id) + THEN + RAISE NOTICE 'Perm check failed for user % on %', + user_id, org_setting_type.view_perm; + RETURN summary; + END IF; + END IF; + + -- Perm check succeeded or was not necessary. + summary.value := setting_value; + RETURN summary; +END; +$FUNC$ LANGUAGE PLPGSQL; + + +CREATE OR REPLACE FUNCTION actor.get_cascade_setting_batch( + setting_names TEXT[], org_id INT, user_id INT, workstation_id INT) + RETURNS SETOF actor.cascade_setting_summary AS +$FUNC$ +-- Returns a row per setting matching the setting name order. If no +-- value is applied, NULL is returned to retain name-response ordering. +DECLARE + setting_name TEXT; + summary actor.cascade_setting_summary; +BEGIN + FOREACH setting_name IN ARRAY setting_names LOOP + SELECT INTO summary * FROM actor.get_cascade_setting( + setting_Name, org_id, user_id, workstation_id); + RETURN NEXT summary; + END LOOP; +END; +$FUNC$ LANGUAGE PLPGSQL; + + COMMIT; diff --git a/Open-ILS/src/sql/Pg/950.data.seed-values.sql b/Open-ILS/src/sql/Pg/950.data.seed-values.sql index 4213a0cd5f..b3f0a2c0bc 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -2094,7 +2094,9 @@ INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable) 'VIEW_PERMIT_CHECKOUT', 'VIEW_USER', 'VIEW_USER_FINES_SUMMARY', - 'VIEW_USER_TRANSACTIONS'); + 'VIEW_USER_TRANSACTIONS', + 'APPLY_WORKSTATION_SETTING' + ); INSERT INTO permission.grp_perm_map (grp, perm, depth, grantable) SELECT @@ -18327,3 +18329,893 @@ WHERE tag = '555' AND control_set = 1 AND ahf.heading_purpose = 'related' AND ahf.heading_type = 'genre_form_term'; + +INSERT INTO permission.perm_list (id, code, description) VALUES + (607, 'APPLY_WORKSTATION_SETTING', + oils_i18n_gettext(607, 'APPLY_WORKSTATION_SETTING', 'ppl', 'description')); + +INSERT INTO config.workstation_setting_type (name, grp, datatype, label) +VALUES ( + 'eg.circ.checkin.no_precat_alert', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.no_precat_alert', + 'Checkin: Ignore Precataloged Items', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.noop', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.noop', + 'Checkin: Suppress Holds and Transits', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.void_overdues', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.void_overdues', + 'Checkin: Amnesty Mode', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.auto_print_holds_transits', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.auto_print_holds_transits', + 'Checkin: Auto-Print Holds and Transits', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.clear_expired', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.clear_expired', + 'Checkin: Clear Holds Shelf', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.retarget_holds', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.retarget_holds', + 'Checkin: Retarget Local Holds', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.retarget_holds_all', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.retarget_holds_all', + 'Checkin: Retarget All Statuses', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.hold_as_transit', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.hold_as_transit', + 'Checkin: Capture Local Holds as Transits', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.manual_float', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.manual_float', + 'Checkin: Manual Floating Active', + 'cwst', 'label' + ) +), ( + 'eg.circ.patron.summary.collapse', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.patron.summary.collapse', + 'Collaps Patron Summary Display', + 'cwst', 'label' + ) +), ( + 'circ.bills.receiptonpay', 'circ', 'bool', + oils_i18n_gettext( + 'circ.bills.receiptonpay', + 'Print Receipt On Payment', + 'cwst', 'label' + ) +), ( + 'circ.renew.strict_barcode', 'circ', 'bool', + oils_i18n_gettext( + 'circ.renew.strict_barcode', + 'Renew: Strict Barcode', + 'cwst', 'label' + ) +), ( + 'circ.checkin.strict_barcode', 'circ', 'bool', + oils_i18n_gettext( + 'circ.checkin.strict_barcode', + 'Checkin: Strict Barcode', + 'cwst', 'label' + ) +), ( + 'cat.holdings_show_copies', 'cat', 'bool', + oils_i18n_gettext( + 'cat.holdings_show_copies', + 'Holdings View Show Copies', + 'cwst', 'label' + ) +), ( + 'cat.holdings_show_empty', 'cat', 'bool', + oils_i18n_gettext( + 'cat.holdings_show_empty', + 'Holdings View Show Empty Volumes', + 'cwst', 'label' + ) +), ( + 'cat.holdings_show_vols', 'cat', 'bool', + oils_i18n_gettext( + 'cat.holdings_show_vols', + 'Holdings View Show Volumes', + 'cwst', 'label' + ) +), ( + 'eg.circ.patron.search.include_inactive', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.patron.search.include_inactive', + 'Patron Search Include Inactive', + 'cwst', 'label' + ) +), ( + 'eg.circ.patron.search.show_extras', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.patron.search.show_extras', + 'Patron Search Show Extra Search Options', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.checkin.checkin', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.checkin.checkin', + 'Grid Config: circ.checkin.checkin', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.checkin.capture', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.checkin.capture', + 'Grid Config: circ.checkin.capture', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.copy_tag_type', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.copy_tag_type', + 'Grid Config: admin.server.config.copy_tag_type', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.metabib_field_virtual_map.grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.metabib_field_virtual_map.grid', + 'Grid Config: admin.server.config.metabib_field_virtual_map.grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.metabib_field.grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.metabib_field.grid', + 'Grid Config: admin.server.config.metabib_field.grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.marc_field', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.marc_field', + 'Grid Config: admin.server.config.marc_field', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.asset.copy_tag', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.asset.copy_tag', + 'Grid Config: admin.server.asset.copy_tag', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.local.circ.neg_balance_users', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.local.circ.neg_balance_users', + 'Grid Config: admin.local.circ.neg_balance_users', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.local.rating.badge', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.local.rating.badge', + 'Grid Config: admin.local.rating.badge', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.workstation.work_log', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.workstation.work_log', + 'Grid Config: admin.workstation.work_log', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.workstation.patron_log', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.workstation.patron_log', + 'Grid Config: admin.workstation.patron_log', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.serials.pattern_template', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.serials.pattern_template', + 'Grid Config: admin.serials.pattern_template', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.copy_templates', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.copy_templates', + 'Grid Config: serials.copy_templates', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.record_overlay.holdings', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.record_overlay.holdings', + 'Grid Config: cat.record_overlay.holdings', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.record.search', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.record.search', + 'Grid Config: cat.bucket.record.search', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.record.view', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.record.view', + 'Grid Config: cat.bucket.record.view', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.record.pending', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.record.pending', + 'Grid Config: cat.bucket.record.pending', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.copy.view', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.copy.view', + 'Grid Config: cat.bucket.copy.view', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.copy.pending', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.copy.pending', + 'Grid Config: cat.bucket.copy.pending', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.items', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.items', + 'Grid Config: cat.items', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.volcopy.copies', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.volcopy.copies', + 'Grid Config: cat.volcopy.copies', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.volcopy.copies.complete', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.volcopy.copies.complete', + 'Grid Config: cat.volcopy.copies.complete', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.peer_bibs', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.peer_bibs', + 'Grid Config: cat.peer_bibs', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.catalog.holds', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.catalog.holds', + 'Grid Config: cat.catalog.holds', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.holdings', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.holdings', + 'Grid Config: cat.holdings', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.z3950_results', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.z3950_results', + 'Grid Config: cat.z3950_results', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.holds.shelf', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.holds.shelf', + 'Grid Config: circ.holds.shelf', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.holds.pull', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.holds.pull', + 'Grid Config: circ.holds.pull', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.in_house_use', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.in_house_use', + 'Grid Config: circ.in_house_use', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.renew', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.renew', + 'Grid Config: circ.renew', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.transits.list', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.transits.list', + 'Grid Config: circ.transits.list', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.holds', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.holds', + 'Grid Config: circ.patron.holds', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.pending_patrons.list', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.pending_patrons.list', + 'Grid Config: circ.pending_patrons.list', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.items_out.noncat', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.items_out.noncat', + 'Grid Config: circ.patron.items_out.noncat', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.items_out', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.items_out', + 'Grid Config: circ.patron.items_out', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.billhistory_payments', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.billhistory_payments', + 'Grid Config: circ.patron.billhistory_payments', + 'cwst', 'label' + ) +), ( + 'eg.grid.user.bucket.view', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.user.bucket.view', + 'Grid Config: user.bucket.view', + 'cwst', 'label' + ) +), ( + 'eg.grid.user.bucket.pending', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.user.bucket.pending', + 'Grid Config: user.bucket.pending', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.staff_messages', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.staff_messages', + 'Grid Config: circ.patron.staff_messages', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.archived_messages', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.archived_messages', + 'Grid Config: circ.patron.archived_messages', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.bills', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.bills', + 'Grid Config: circ.patron.bills', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.checkout', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.checkout', + 'Grid Config: circ.patron.checkout', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.mfhd_grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.mfhd_grid', + 'Grid Config: serials.mfhd_grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.view_item_grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.view_item_grid', + 'Grid Config: serials.view_item_grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.dist_stream_grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.dist_stream_grid', + 'Grid Config: serials.dist_stream_grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.search', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.search', + 'Grid Config: circ.patron.search', + 'cwst', 'label' + ) +), ( + 'eg.cat.record.summary.collapse', 'gui', 'bool', + oils_i18n_gettext( + 'eg.cat.record.summary.collapse', + 'Collapse Bib Record Summary', + 'cwst', 'label' + ) +), ( + 'cat.marcedit.flateditor', 'gui', 'bool', + oils_i18n_gettext( + 'cat.marcedit.flateditor', + 'Use Flat MARC Editor', + 'cwst', 'label' + ) +), ( + 'cat.marcedit.stack_subfields', 'gui', 'bool', + oils_i18n_gettext( + 'cat.marcedit.stack_subfields', + 'MARC Editor Stack Subfields', + 'cwst', 'label' + ) +), ( + 'eg.offline.print_receipt', 'gui', 'bool', + oils_i18n_gettext( + 'eg.offline.print_receipt', + 'Offline Print Receipt', + 'cwst', 'label' + ) +), ( + 'eg.offline.strict_barcode', 'gui', 'bool', + oils_i18n_gettext( + 'eg.offline.strict_barcode', + 'Offline Use Strict Barcode', + 'cwst', 'label' + ) +), ( + 'cat.default_bib_marc_template', 'gui', 'string', + oils_i18n_gettext( + 'cat.default_bib_marc_template', + 'Default MARC Template', + 'cwst', 'label' + ) +), ( + 'eg.audio.disable', 'gui', 'bool', + oils_i18n_gettext( + 'eg.audio.disable', + 'Disable Staff Client Notification Audio', + 'cwst', 'label' + ) +), ( + 'eg.search.adv_pane', 'gui', 'string', + oils_i18n_gettext( + 'eg.search.adv_pane', + 'Catalog Advanced Search Default Pane', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.bills_current', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.bills_current', + 'Print Template Context: bills_current', + 'cwst', 'label' + ) +), ( + 'eg.print.template.bills_current', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.bills_current', + 'Print Template: bills_current', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.bills_historical', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.bills_historical', + 'Print Template Context: bills_historical', + 'cwst', 'label' + ) +), ( + 'eg.print.template.bills_historical', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.bills_historical', + 'Print Template: bills_historical', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.bill_payment', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.bill_payment', + 'Print Template Context: bill_payment', + 'cwst', 'label' + ) +), ( + 'eg.print.template.bill_payment', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.bill_payment', + 'Print Template: bill_payment', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.checkin', + 'Print Template Context: checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template.checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.checkin', + 'Print Template: checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.checkout', + 'Print Template Context: checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template.checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.checkout', + 'Print Template: checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_transit_slip', + 'Print Template Context: hold_transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_transit_slip', + 'Print Template: hold_transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_shelf_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_shelf_slip', + 'Print Template Context: hold_shelf_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_shelf_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_shelf_slip', + 'Print Template: hold_shelf_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.holds_for_bib', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.holds_for_bib', + 'Print Template Context: holds_for_bib', + 'cwst', 'label' + ) +), ( + 'eg.print.template.holds_for_bib', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.holds_for_bib', + 'Print Template: holds_for_bib', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.holds_for_patron', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.holds_for_patron', + 'Print Template Context: holds_for_patron', + 'cwst', 'label' + ) +), ( + 'eg.print.template.holds_for_patron', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.holds_for_patron', + 'Print Template: holds_for_patron', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_pull_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_pull_list', + 'Print Template Context: hold_pull_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_pull_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_pull_list', + 'Print Template: hold_pull_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_shelf_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_shelf_list', + 'Print Template Context: hold_shelf_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_shelf_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_shelf_list', + 'Print Template: hold_shelf_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.in_house_use_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.in_house_use_list', + 'Print Template Context: in_house_use_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.in_house_use_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.in_house_use_list', + 'Print Template: in_house_use_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.item_status', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.item_status', + 'Print Template Context: item_status', + 'cwst', 'label' + ) +), ( + 'eg.print.template.item_status', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.item_status', + 'Print Template: item_status', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.items_out', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.items_out', + 'Print Template Context: items_out', + 'cwst', 'label' + ) +), ( + 'eg.print.template.items_out', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.items_out', + 'Print Template: items_out', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.patron_address', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.patron_address', + 'Print Template Context: patron_address', + 'cwst', 'label' + ) +), ( + 'eg.print.template.patron_address', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.patron_address', + 'Print Template: patron_address', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.patron_data', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.patron_data', + 'Print Template Context: patron_data', + 'cwst', 'label' + ) +), ( + 'eg.print.template.patron_data', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.patron_data', + 'Print Template: patron_data', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.patron_note', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.patron_note', + 'Print Template Context: patron_note', + 'cwst', 'label' + ) +), ( + 'eg.print.template.patron_note', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.patron_note', + 'Print Template: patron_note', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.renew', + 'Print Template Context: renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template.renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.renew', + 'Print Template: renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.transit_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.transit_list', + 'Print Template Context: transit_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.transit_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.transit_list', + 'Print Template: transit_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.transit_slip', + 'Print Template Context: transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template.transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.transit_slip', + 'Print Template: transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_checkout', + 'Print Template Context: offline_checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_checkout', + 'Print Template: offline_checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_renew', + 'Print Template Context: offline_renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_renew', + 'Print Template: offline_renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_checkin', + 'Print Template Context: offline_checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_checkin', + 'Print Template: offline_checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_in_house_use', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_in_house_use', + 'Print Template Context: offline_in_house_use', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_in_house_use', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_in_house_use', + 'Print Template: offline_in_house_use', + 'cwst', 'label' + ) +), ( + 'eg.serials.stream_names', 'gui', 'array', + oils_i18n_gettext( + 'eg.serials.stream_names', + 'Serials Local Stream Names', + 'cwst', 'label' + ) +), ( + 'eg.serials.items.do_print_routing_lists', 'gui', 'bool', + oils_i18n_gettext( + 'eg.serials.items.do_print_routing_lists', + 'Serials Print Routing Lists', + 'cwst', 'label' + ) +), ( + 'eg.serials.items.receive_and_barcode', 'gui', 'bool', + oils_i18n_gettext( + 'eg.serials.items.receive_and_barcode', + 'Serials Barcode On Receive', + 'cwst', 'label' + ) +); + + +-- More values with fm_class'es +INSERT INTO config.workstation_setting_type (name, grp, datatype, fm_class, label) +VALUES ( + 'eg.search.search_lib', 'gui', 'link', 'aou', + oils_i18n_gettext( + 'eg.search.search_lib', + 'Staff Catalog Default Search Library', + 'cwst', 'label' + ) +), ( + 'eg.search.pref_lib', 'gui', 'link', 'aou', + oils_i18n_gettext( + 'eg.search.pref_lib', + 'Staff Catalog Preferred Library', + 'cwst', 'label' + ) +); + + + diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.workstation-settings.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.workstation-settings.sql new file mode 100644 index 0000000000..11ff097c3c --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.schema.workstation-settings.sql @@ -0,0 +1,227 @@ +BEGIN; + +CREATE TYPE actor.cascade_setting_summary AS ( + name TEXT, + value JSON, + has_org_setting BOOLEAN, + has_user_setting BOOLEAN, + has_workstation_setting BOOLEAN +); + +-- SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +CREATE TABLE config.workstation_setting_type ( + name TEXT PRIMARY KEY, + label TEXT UNIQUE NOT NULL, + grp TEXT REFERENCES config.settings_group (name), + description TEXT, + datatype TEXT NOT NULL DEFAULT 'string', + fm_class TEXT, + -- + -- define valid datatypes + -- + CONSTRAINT cwst_valid_datatype CHECK ( datatype IN + ( 'bool', 'integer', 'float', 'currency', 'interval', + 'date', 'string', 'object', 'array', 'link' ) ), + -- + -- fm_class is meaningful only for 'link' datatype + -- + CONSTRAINT cwst_no_empty_link CHECK + ( ( datatype = 'link' AND fm_class IS NOT NULL ) OR + ( datatype <> 'link' AND fm_class IS NULL ) ) +); + +CREATE TABLE actor.workstation_setting ( + id SERIAL PRIMARY KEY, + workstation INT NOT NULL REFERENCES actor.workstation (id) + ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED, + name TEXT NOT NULL REFERENCES config.workstation_setting_type (name) + ON DELETE CASCADE ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED, + value JSON NOT NULL +); + + +CREATE INDEX actor_workstation_setting_workstation_idx + ON actor.workstation_setting (workstation); + +CREATE OR REPLACE FUNCTION config.setting_is_user_or_ws() +RETURNS TRIGGER AS $FUNC$ +BEGIN + + IF TG_TABLE_NAME = 'usr_setting_type' THEN + PERFORM TRUE FROM config.workstation_setting_type cwst + WHERE cwst.name = NEW.name; + IF NOT FOUND THEN + RETURN NULL; + END IF; + END IF; + + IF TG_TABLE_NAME = 'workstation_setting_type' THEN + PERFORM TRUE FROM config.usr_setting_type cust + WHERE cust.name = NEW.name; + IF NOT FOUND THEN + RETURN NULL; + END IF; + END IF; + + RAISE EXCEPTION + '% Cannot be used as both a user setting and a workstation setting.', + NEW.name; +END; +$FUNC$ LANGUAGE PLPGSQL STABLE; + +CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws + AFTER INSERT OR UPDATE ON config.usr_setting_type + FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws(); + +CREATE CONSTRAINT TRIGGER check_setting_is_usr_or_ws + AFTER INSERT OR UPDATE ON config.workstation_setting_type + FOR EACH ROW EXECUTE PROCEDURE config.setting_is_user_or_ws(); + +CREATE OR REPLACE FUNCTION actor.get_cascade_setting( + setting_name TEXT, org_id INT, user_id INT, workstation_id INT) + RETURNS actor.cascade_setting_summary AS +$FUNC$ +DECLARE + setting_value JSON; + summary actor.cascade_setting_summary; + org_setting_type config.org_unit_setting_type%ROWTYPE; +BEGIN + + summary.name := setting_name; + + -- Collect the org setting type status first in case we exit early. + -- The existance of an org setting type is not considered + -- privileged information. + SELECT INTO org_setting_type * + FROM config.org_unit_setting_type WHERE name = setting_name; + IF FOUND THEN + summary.has_org_setting := TRUE; + ELSE + summary.has_org_setting := FALSE; + END IF; + + -- User and workstation settings have the same priority. + -- Start with user settings since that's the simplest code path. + -- The workstation_id is ignored if no user_id is provided. + IF user_id IS NOT NULL THEN + + SELECT INTO summary.value value FROM actor.usr_setting + WHERE usr = user_id AND name = setting_name; + + IF FOUND THEN + -- if we have a value, we have a setting type + summary.has_user_setting := TRUE; + + IF workstation_id IS NOT NULL THEN + -- Only inform the caller about the workstation + -- setting type disposition when a workstation id is + -- provided. Otherwise, it's NULL to indicate UNKNOWN. + summary.has_workstation_setting := FALSE; + END IF; + + RETURN summary; + END IF; + + -- no user setting value, but a setting type may exist + SELECT INTO summary.has_user_setting EXISTS ( + SELECT TRUE FROM config.usr_setting_type + WHERE name = setting_name + ); + + IF workstation_id IS NOT NULL THEN + + IF NOT summary.has_user_setting THEN + -- A workstation setting type may only exist when a user + -- setting type does not. + + SELECT INTO summary.value value + FROM actor.workstation_setting + WHERE workstation = workstation_id AND name = setting_name; + + IF FOUND THEN + -- if we have a value, we have a setting type + summary.has_workstation_setting := TRUE; + RETURN summary; + END IF; + + -- no value, but a setting type may exist + SELECT INTO summary.has_workstation_setting EXISTS ( + SELECT TRUE FROM config.workstation_setting_type + WHERE name = setting_name + ); + END IF; + + -- Finally make use of the workstation to determine the org + -- unit if none is provided. + IF org_id IS NULL AND summary.has_org_setting THEN + SELECT INTO org_id owning_lib + FROM actor.workstation WHERE id = workstation_id; + END IF; + END IF; + END IF; + + -- Some org unit settings are protected by a view permission. + -- First see if we have any data that needs protecting, then + -- check the permission if needed. + + IF NOT summary.has_org_setting THEN + RETURN summary; + END IF; + + -- avoid putting the value into the summary until we confirm + -- the value should be visible to the caller. + SELECT INTO setting_value value + FROM actor.org_unit_ancestor_setting(setting_name, org_id); + + IF NOT FOUND THEN + -- No value found -- perm check is irrelevant. + RETURN summary; + END IF; + + IF org_setting_type.view_perm IS NOT NULL THEN + + IF user_id IS NULL THEN + RAISE NOTICE 'Perm check required but no user_id provided'; + RETURN summary; + END IF; + + IF NOT permission.usr_has_perm( + user_id, (SELECT code FROM permission.perm_list + WHERE id = org_setting_type.view_perm), org_id) + THEN + RAISE NOTICE 'Perm check failed for user % on %', + user_id, org_setting_type.view_perm; + RETURN summary; + END IF; + END IF; + + -- Perm check succeeded or was not necessary. + summary.value := setting_value; + RETURN summary; +END; +$FUNC$ LANGUAGE PLPGSQL; + + +CREATE OR REPLACE FUNCTION actor.get_cascade_setting_batch( + setting_names TEXT[], org_id INT, user_id INT, workstation_id INT) + RETURNS SETOF actor.cascade_setting_summary AS +$FUNC$ +-- Returns a row per setting matching the setting name order. If no +-- value is applied, NULL is returned to retain name-response ordering. +DECLARE + setting_name TEXT; + summary actor.cascade_setting_summary; +BEGIN + FOREACH setting_name IN ARRAY setting_names LOOP + SELECT INTO summary * FROM actor.get_cascade_setting( + setting_Name, org_id, user_id, workstation_id); + RETURN NEXT summary; + END LOOP; +END; +$FUNC$ LANGUAGE PLPGSQL; + +COMMIT; + + + diff --git a/Open-ILS/src/sql/Pg/upgrade/YYYY.data.workstation-settings.sql b/Open-ILS/src/sql/Pg/upgrade/YYYY.data.workstation-settings.sql new file mode 100644 index 0000000000..d50c3717f9 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/YYYY.data.workstation-settings.sql @@ -0,0 +1,893 @@ +BEGIN; + +INSERT INTO permission.perm_list (id, code, description) VALUES + (607, 'APPLY_WORKSTATION_SETTING', + oils_i18n_gettext(607, 'APPLY_WORKSTATION_SETTING', 'ppl', 'description')); + +INSERT INTO config.workstation_setting_type (name, grp, datatype, label) +VALUES ( + 'eg.circ.checkin.no_precat_alert', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.no_precat_alert', + 'Checkin: Ignore Precataloged Items', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.noop', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.noop', + 'Checkin: Suppress Holds and Transits', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.void_overdues', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.void_overdues', + 'Checkin: Amnesty Mode', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.auto_print_holds_transits', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.auto_print_holds_transits', + 'Checkin: Auto-Print Holds and Transits', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.clear_expired', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.clear_expired', + 'Checkin: Clear Holds Shelf', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.retarget_holds', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.retarget_holds', + 'Checkin: Retarget Local Holds', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.retarget_holds_all', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.retarget_holds_all', + 'Checkin: Retarget All Statuses', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.hold_as_transit', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.hold_as_transit', + 'Checkin: Capture Local Holds as Transits', + 'cwst', 'label' + ) +), ( + 'eg.circ.checkin.manual_float', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.checkin.manual_float', + 'Checkin: Manual Floating Active', + 'cwst', 'label' + ) +), ( + 'eg.circ.patron.summary.collapse', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.patron.summary.collapse', + 'Collaps Patron Summary Display', + 'cwst', 'label' + ) +), ( + 'circ.bills.receiptonpay', 'circ', 'bool', + oils_i18n_gettext( + 'circ.bills.receiptonpay', + 'Print Receipt On Payment', + 'cwst', 'label' + ) +), ( + 'circ.renew.strict_barcode', 'circ', 'bool', + oils_i18n_gettext( + 'circ.renew.strict_barcode', + 'Renew: Strict Barcode', + 'cwst', 'label' + ) +), ( + 'circ.checkin.strict_barcode', 'circ', 'bool', + oils_i18n_gettext( + 'circ.checkin.strict_barcode', + 'Checkin: Strict Barcode', + 'cwst', 'label' + ) +), ( + 'cat.holdings_show_copies', 'cat', 'bool', + oils_i18n_gettext( + 'cat.holdings_show_copies', + 'Holdings View Show Copies', + 'cwst', 'label' + ) +), ( + 'cat.holdings_show_empty', 'cat', 'bool', + oils_i18n_gettext( + 'cat.holdings_show_empty', + 'Holdings View Show Empty Volumes', + 'cwst', 'label' + ) +), ( + 'cat.holdings_show_vols', 'cat', 'bool', + oils_i18n_gettext( + 'cat.holdings_show_vols', + 'Holdings View Show Volumes', + 'cwst', 'label' + ) +), ( + 'eg.circ.patron.search.include_inactive', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.patron.search.include_inactive', + 'Patron Search Include Inactive', + 'cwst', 'label' + ) +), ( + 'eg.circ.patron.search.show_extras', 'circ', 'bool', + oils_i18n_gettext( + 'eg.circ.patron.search.show_extras', + 'Patron Search Show Extra Search Options', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.checkin.checkin', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.checkin.checkin', + 'Grid Config: circ.checkin.checkin', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.checkin.capture', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.checkin.capture', + 'Grid Config: circ.checkin.capture', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.copy_tag_type', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.copy_tag_type', + 'Grid Config: admin.server.config.copy_tag_type', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.metabib_field_virtual_map.grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.metabib_field_virtual_map.grid', + 'Grid Config: admin.server.config.metabib_field_virtual_map.grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.metabib_field.grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.metabib_field.grid', + 'Grid Config: admin.server.config.metabib_field.grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.config.marc_field', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.config.marc_field', + 'Grid Config: admin.server.config.marc_field', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.server.asset.copy_tag', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.server.asset.copy_tag', + 'Grid Config: admin.server.asset.copy_tag', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.local.circ.neg_balance_users', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.local.circ.neg_balance_users', + 'Grid Config: admin.local.circ.neg_balance_users', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.local.rating.badge', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.local.rating.badge', + 'Grid Config: admin.local.rating.badge', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.workstation.work_log', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.workstation.work_log', + 'Grid Config: admin.workstation.work_log', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.workstation.patron_log', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.workstation.patron_log', + 'Grid Config: admin.workstation.patron_log', + 'cwst', 'label' + ) +), ( + 'eg.grid.admin.serials.pattern_template', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.admin.serials.pattern_template', + 'Grid Config: admin.serials.pattern_template', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.copy_templates', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.copy_templates', + 'Grid Config: serials.copy_templates', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.record_overlay.holdings', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.record_overlay.holdings', + 'Grid Config: cat.record_overlay.holdings', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.record.search', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.record.search', + 'Grid Config: cat.bucket.record.search', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.record.view', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.record.view', + 'Grid Config: cat.bucket.record.view', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.record.pending', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.record.pending', + 'Grid Config: cat.bucket.record.pending', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.copy.view', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.copy.view', + 'Grid Config: cat.bucket.copy.view', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.bucket.copy.pending', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.bucket.copy.pending', + 'Grid Config: cat.bucket.copy.pending', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.items', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.items', + 'Grid Config: cat.items', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.volcopy.copies', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.volcopy.copies', + 'Grid Config: cat.volcopy.copies', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.volcopy.copies.complete', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.volcopy.copies.complete', + 'Grid Config: cat.volcopy.copies.complete', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.peer_bibs', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.peer_bibs', + 'Grid Config: cat.peer_bibs', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.catalog.holds', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.catalog.holds', + 'Grid Config: cat.catalog.holds', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.holdings', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.holdings', + 'Grid Config: cat.holdings', + 'cwst', 'label' + ) +), ( + 'eg.grid.cat.z3950_results', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.cat.z3950_results', + 'Grid Config: cat.z3950_results', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.holds.shelf', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.holds.shelf', + 'Grid Config: circ.holds.shelf', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.holds.pull', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.holds.pull', + 'Grid Config: circ.holds.pull', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.in_house_use', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.in_house_use', + 'Grid Config: circ.in_house_use', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.renew', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.renew', + 'Grid Config: circ.renew', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.transits.list', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.transits.list', + 'Grid Config: circ.transits.list', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.holds', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.holds', + 'Grid Config: circ.patron.holds', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.pending_patrons.list', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.pending_patrons.list', + 'Grid Config: circ.pending_patrons.list', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.items_out.noncat', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.items_out.noncat', + 'Grid Config: circ.patron.items_out.noncat', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.items_out', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.items_out', + 'Grid Config: circ.patron.items_out', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.billhistory_payments', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.billhistory_payments', + 'Grid Config: circ.patron.billhistory_payments', + 'cwst', 'label' + ) +), ( + 'eg.grid.user.bucket.view', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.user.bucket.view', + 'Grid Config: user.bucket.view', + 'cwst', 'label' + ) +), ( + 'eg.grid.user.bucket.pending', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.user.bucket.pending', + 'Grid Config: user.bucket.pending', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.staff_messages', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.staff_messages', + 'Grid Config: circ.patron.staff_messages', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.archived_messages', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.archived_messages', + 'Grid Config: circ.patron.archived_messages', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.bills', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.bills', + 'Grid Config: circ.patron.bills', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.checkout', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.checkout', + 'Grid Config: circ.patron.checkout', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.mfhd_grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.mfhd_grid', + 'Grid Config: serials.mfhd_grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.view_item_grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.view_item_grid', + 'Grid Config: serials.view_item_grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.serials.dist_stream_grid', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.serials.dist_stream_grid', + 'Grid Config: serials.dist_stream_grid', + 'cwst', 'label' + ) +), ( + 'eg.grid.circ.patron.search', 'gui', 'object', + oils_i18n_gettext( + 'eg.grid.circ.patron.search', + 'Grid Config: circ.patron.search', + 'cwst', 'label' + ) +), ( + 'eg.cat.record.summary.collapse', 'gui', 'bool', + oils_i18n_gettext( + 'eg.cat.record.summary.collapse', + 'Collapse Bib Record Summary', + 'cwst', 'label' + ) +), ( + 'cat.marcedit.flateditor', 'gui', 'bool', + oils_i18n_gettext( + 'cat.marcedit.flateditor', + 'Use Flat MARC Editor', + 'cwst', 'label' + ) +), ( + 'cat.marcedit.stack_subfields', 'gui', 'bool', + oils_i18n_gettext( + 'cat.marcedit.stack_subfields', + 'MARC Editor Stack Subfields', + 'cwst', 'label' + ) +), ( + 'eg.offline.print_receipt', 'gui', 'bool', + oils_i18n_gettext( + 'eg.offline.print_receipt', + 'Offline Print Receipt', + 'cwst', 'label' + ) +), ( + 'eg.offline.strict_barcode', 'gui', 'bool', + oils_i18n_gettext( + 'eg.offline.strict_barcode', + 'Offline Use Strict Barcode', + 'cwst', 'label' + ) +), ( + 'cat.default_bib_marc_template', 'gui', 'string', + oils_i18n_gettext( + 'cat.default_bib_marc_template', + 'Default MARC Template', + 'cwst', 'label' + ) +), ( + 'eg.audio.disable', 'gui', 'bool', + oils_i18n_gettext( + 'eg.audio.disable', + 'Disable Staff Client Notification Audio', + 'cwst', 'label' + ) +), ( + 'eg.search.adv_pane', 'gui', 'string', + oils_i18n_gettext( + 'eg.search.adv_pane', + 'Catalog Advanced Search Default Pane', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.bills_current', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.bills_current', + 'Print Template Context: bills_current', + 'cwst', 'label' + ) +), ( + 'eg.print.template.bills_current', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.bills_current', + 'Print Template: bills_current', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.bills_historical', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.bills_historical', + 'Print Template Context: bills_historical', + 'cwst', 'label' + ) +), ( + 'eg.print.template.bills_historical', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.bills_historical', + 'Print Template: bills_historical', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.bill_payment', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.bill_payment', + 'Print Template Context: bill_payment', + 'cwst', 'label' + ) +), ( + 'eg.print.template.bill_payment', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.bill_payment', + 'Print Template: bill_payment', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.checkin', + 'Print Template Context: checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template.checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.checkin', + 'Print Template: checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.checkout', + 'Print Template Context: checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template.checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.checkout', + 'Print Template: checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_transit_slip', + 'Print Template Context: hold_transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_transit_slip', + 'Print Template: hold_transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_shelf_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_shelf_slip', + 'Print Template Context: hold_shelf_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_shelf_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_shelf_slip', + 'Print Template: hold_shelf_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.holds_for_bib', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.holds_for_bib', + 'Print Template Context: holds_for_bib', + 'cwst', 'label' + ) +), ( + 'eg.print.template.holds_for_bib', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.holds_for_bib', + 'Print Template: holds_for_bib', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.holds_for_patron', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.holds_for_patron', + 'Print Template Context: holds_for_patron', + 'cwst', 'label' + ) +), ( + 'eg.print.template.holds_for_patron', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.holds_for_patron', + 'Print Template: holds_for_patron', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_pull_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_pull_list', + 'Print Template Context: hold_pull_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_pull_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_pull_list', + 'Print Template: hold_pull_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.hold_shelf_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.hold_shelf_list', + 'Print Template Context: hold_shelf_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.hold_shelf_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.hold_shelf_list', + 'Print Template: hold_shelf_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.in_house_use_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.in_house_use_list', + 'Print Template Context: in_house_use_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.in_house_use_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.in_house_use_list', + 'Print Template: in_house_use_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.item_status', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.item_status', + 'Print Template Context: item_status', + 'cwst', 'label' + ) +), ( + 'eg.print.template.item_status', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.item_status', + 'Print Template: item_status', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.items_out', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.items_out', + 'Print Template Context: items_out', + 'cwst', 'label' + ) +), ( + 'eg.print.template.items_out', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.items_out', + 'Print Template: items_out', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.patron_address', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.patron_address', + 'Print Template Context: patron_address', + 'cwst', 'label' + ) +), ( + 'eg.print.template.patron_address', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.patron_address', + 'Print Template: patron_address', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.patron_data', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.patron_data', + 'Print Template Context: patron_data', + 'cwst', 'label' + ) +), ( + 'eg.print.template.patron_data', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.patron_data', + 'Print Template: patron_data', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.patron_note', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.patron_note', + 'Print Template Context: patron_note', + 'cwst', 'label' + ) +), ( + 'eg.print.template.patron_note', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.patron_note', + 'Print Template: patron_note', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.renew', + 'Print Template Context: renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template.renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.renew', + 'Print Template: renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.transit_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.transit_list', + 'Print Template Context: transit_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template.transit_list', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.transit_list', + 'Print Template: transit_list', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.transit_slip', + 'Print Template Context: transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template.transit_slip', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.transit_slip', + 'Print Template: transit_slip', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_checkout', + 'Print Template Context: offline_checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_checkout', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_checkout', + 'Print Template: offline_checkout', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_renew', + 'Print Template Context: offline_renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_renew', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_renew', + 'Print Template: offline_renew', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_checkin', + 'Print Template Context: offline_checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_checkin', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_checkin', + 'Print Template: offline_checkin', + 'cwst', 'label' + ) +), ( + 'eg.print.template_context.offline_in_house_use', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template_context.offline_in_house_use', + 'Print Template Context: offline_in_house_use', + 'cwst', 'label' + ) +), ( + 'eg.print.template.offline_in_house_use', 'gui', 'string', + oils_i18n_gettext( + 'eg.print.template.offline_in_house_use', + 'Print Template: offline_in_house_use', + 'cwst', 'label' + ) +), ( + 'eg.serials.stream_names', 'gui', 'array', + oils_i18n_gettext( + 'eg.serials.stream_names', + 'Serials Local Stream Names', + 'cwst', 'label' + ) +), ( + 'eg.serials.items.do_print_routing_lists', 'gui', 'bool', + oils_i18n_gettext( + 'eg.serials.items.do_print_routing_lists', + 'Serials Print Routing Lists', + 'cwst', 'label' + ) +), ( + 'eg.serials.items.receive_and_barcode', 'gui', 'bool', + oils_i18n_gettext( + 'eg.serials.items.receive_and_barcode', + 'Serials Barcode On Receive', + 'cwst', 'label' + ) +); + + +-- More values with fm_class'es +INSERT INTO config.workstation_setting_type (name, grp, datatype, fm_class, label) +VALUES ( + 'eg.search.search_lib', 'gui', 'link', 'aou', + oils_i18n_gettext( + 'eg.search.search_lib', + 'Staff Catalog Default Search Library', + 'cwst', 'label' + ) +), ( + 'eg.search.pref_lib', 'gui', 'link', 'aou', + oils_i18n_gettext( + 'eg.search.pref_lib', + 'Staff Catalog Preferred Library', + 'cwst', 'label' + ) +); + + +COMMIT; + + diff --git a/Open-ILS/src/templates/staff/admin/workstation/index.tt2 b/Open-ILS/src/templates/staff/admin/workstation/index.tt2 index 44842eaf37..3f34c1f9fd 100644 --- a/Open-ILS/src/templates/staff/admin/workstation/index.tt2 +++ b/Open-ILS/src/templates/staff/admin/workstation/index.tt2 @@ -23,6 +23,8 @@ angular.module('egCoreMod').run(['egStrings', function(s) { s.PRINT_TEMPLATES_FAIL_IMPORT = "[% l('Failed to import any print template(s)') %]"; s.HATCH_SETTINGS_MIGRATION_SUCCESS = "[% l('Settings successfully migrated') %]"; s.HATCH_SETTINGS_MIGRATION_FAILURE = "[% l('Settings migration failed') %]"; + s.HATCH_SERVER_SETTINGS_MIGRATION_CONFIRM = + "[% l('This will delete the local version all settings configured to live on the server. Continue?') %]"; }]); </script> [% END %] diff --git a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js index 083b5dde60..98dc9b77e8 100644 --- a/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js +++ b/Open-ILS/web/js/ui/default/staff/admin/workstation/app.js @@ -785,7 +785,9 @@ function($scope , $q , egCore , egConfirmDialog) { egCore.hatch.removeLocalItem(key); refreshKeys(); } else { - egCore.hatch.removeItem(key) + // Honor requests to remove items from Hatch even + // when Hatch is configured for data storage. + egCore.hatch.removeRemoteItem(key) .then(function() { refreshKeys() }); } }, diff --git a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js index 6a79396a74..103a8e06b3 100644 --- a/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js +++ b/Open-ILS/web/js/ui/default/staff/circ/checkin/app.js @@ -49,6 +49,7 @@ function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , eg $scope.grid_persist_key = $scope.is_capture ? 'circ.checkin.capture' : 'circ.checkin.checkin'; + // TODO: add this to the setting batch lookup below egCore.hatch.getItem('circ.checkin.strict_barcode') .then(function(sb){ $scope.strict_barcode = sb }); @@ -88,9 +89,15 @@ function($scope , $q , $window , $location , $timeout , egCore , checkinSvc , eg } // set modifiers from stored preferences - angular.forEach(modifiers, function(mod) { - egCore.hatch.getItem('eg.circ.checkin.' + mod) - .then(function(val) { if (val) $scope.modifiers[mod] = true }); + var snames = modifiers.map(function(m) {return 'eg.circ.checkin.' + m;}); + egCore.hatch.getItemBatch(snames).then(function(settings) { + angular.forEach(settings, function(val, key) { + if (val === true) { + var parts = key.split('.') + var mod = parts.pop(); + $scope.modifiers[mod] = true; + } + }) }); // set / unset a checkin modifier diff --git a/Open-ILS/web/js/ui/default/staff/services/hatch.js b/Open-ILS/web/js/ui/default/staff/services/hatch.js index f7083a7b67..d35c3f103a 100644 --- a/Open-ILS/web/js/ui/default/staff/services/hatch.js +++ b/Open-ILS/web/js/ui/default/staff/services/hatch.js @@ -25,18 +25,24 @@ angular.module('egCoreMod') .factory('egHatch', - ['$q','$window','$timeout','$interpolate','$cookies', - function($q , $window , $timeout , $interpolate , $cookies) { + ['$q','$window','$timeout','$interpolate','$cookies','egNet','$injector', + function($q , $window , $timeout , $interpolate , $cookies , egNet , $injector ) { var service = {}; service.msgId = 1; service.messages = {}; service.hatchAvailable = false; + service.auth = null; // ref to egAuth loaded on-demand to avoid circular ref. + service.disableServerSettings = false; // key/value cache -- avoid unnecessary Hatch extension requests. // Only affects *RemoteItem calls. service.keyCache = {}; + // Keep a local copy of all retrieved setting summaries, which indicate + // which setting types exist for each setting. + service.serverSettingSummaries = {}; + /** * List string prefixes for On-Call storage keys. On-Call keys * are those that can be set/get/remove'd from localStorage when @@ -50,7 +56,7 @@ angular.module('egCoreMod') * at a time and each maintains its own data separately. */ service.onCallPrefixes = ['eg.workstation']; - + // Returns true if the key can be set/get in localStorage even when // Hatch is not available. service.keyIsOnCall = function(key) { @@ -62,6 +68,35 @@ angular.module('egCoreMod') return oncall; } + /** + * Settings with these prefixes will always live in the browser. + */ + service.browserOnlyPrefixes = [ + 'eg.workstation', + 'eg.hatch', + 'eg.cache', + 'current_tag_table_marc21_biblio', + 'FFPos', + 'FFValue' + ]; + + service.keyStoredInBrowser = function(key) { + + if (service.disableServerSettings) { + // When server-side storage is disabled, treat every + // setting like it's stored locally. + return true; + } + + var browserOnly = false; + service.browserOnlyPrefixes.forEach(function(pfx) { + if (key.match(new RegExp('^' + pfx))) + browserOnly = true; + }); + + return browserOnly; + } + // write a message to the Hatch port service.sendToHatch = function(msg) { var msg2 = {}; @@ -198,6 +233,8 @@ angular.module('egCoreMod') ); } + // TODO: once Hatch is printing-only, should probably store + // this preference on the server. service.usePrinting = function() { return service.getLocalItem('eg.hatch.enable.printing'); } @@ -213,20 +250,77 @@ angular.module('egCoreMod') // get the value for a stored item service.getItem = function(key) { - if (!service.useSettings()) - return $q.when(service.getLocalItem(key)); + if (!service.keyStoredInBrowser(key)) { + return service.getServerItem(key); + } + + var deferred = $q.defer(); - if (service.hatchAvailable) - return service.getRemoteItem(key); + service.getBrowserItem(key).then( + function(val) { deferred.resolve(val); }, - if (service.keyIsOnCall(key)) { - console.warn("Unable to getItem from Hatch: " + key + - ". Retrieving item from local storage instead"); + function() { // Hatch error + if (service.keyIsOnCall(key)) { + console.warn("Unable to getItem from Hatch: " + key + + ". Retrieving item from local storage instead"); + deferred.resolve(service.getLocalItem(key)); + } + + deferred.reject("Unable to getItem from Hatch: " + key); + } + ); + + return deferred.promise; + } + + // Collect values in batch. + // For server-stored values espeically, this is more efficient + // than a series of one-off calls. + service.getItemBatch = function(keys) { + var browserKeys = []; + var serverKeys = []; + + // To take full advantage of the getServerItemBatch call, + // we have to know in advance which keys to send to the server + // vs those to handle in the browser. + keys.forEach(function(key) { + if (service.keyStoredInBrowser(key)) { + browserKeys.push(key); + } else { + serverKeys.push(key); + } + }); + + var settings = {}; + + var serverPromise = serverKeys.length === 0 ? $q.when() : + service.getServerItemBatch(serverKeys).then(function(values) { + angular.forEach(values, function(val, key) { + settings[key] = val; + }); + }); + + var browserPromises = []; + browserKeys.forEach(function(key) { + browserPromises.push( + service.getBrowserItem(key).then(function(val) { + settings[key] = val; + }) + ); + }); + + return $q.all(browserPromises.concat(serverPromise)) + .then(function() {return settings}); + } + service.getBrowserItem = function(key) { + if (service.useSettings()) { + if (service.hatchAvailable) { + return service.getRemoteItem(key); + } + } else { return $q.when(service.getLocalItem(key)); } - - console.error("Unable to getItem from Hatch: " + key); return $q.reject(); } @@ -245,7 +339,7 @@ angular.module('egCoreMod') service.getLocalItem = function(key) { var val = $window.localStorage.getItem(key); - if (val == null) return; + if (val === null || val === undefined) return; try { return JSON.parse(val); } catch(E) { @@ -273,23 +367,200 @@ angular.module('egCoreMod') * tmp values are removed during logout or browser close. */ service.setItem = function(key, value) { - if (!service.useSettings()) - return $q.when(service.setLocalItem(key, value)); - if (service.hatchAvailable) - return service.setRemoteItem(key, value); + if (!service.keyStoredInBrowser(key)) { + return service.setServerItem(key, value); + } - if (service.keyIsOnCall(key)) { - console.warn("Unable to setItem in Hatch: " + - key + ". Setting in local storage instead"); + var deferred = $q.defer(); + service.setBrowserItem(key, value).then( + function(val) {deferred.resolve(val);}, + + function() { // Hatch error + + if (service.keyIsOnCall(key)) { + console.warn("Unable to setItem in Hatch: " + + key + ". Setting in local storage instead"); + + deferred.resolve(service.setLocalItem(key, value)); + } + deferred.reject("Unable to setItem in Hatch: " + key); + } + ); + } + service.setBrowserItem = function(key, value) { + if (service.useSettings()) { + if (service.hatchAvailable) { + return service.setRemoteItem(key, value); + } else { + return $q.reject('Unable to get item from hatch'); + } + } else { return $q.when(service.setLocalItem(key, value)); } + } - console.error("Unable to setItem in Hatch: " + key); - return $q.reject(); + service.setServerItem = function(key, value) { + if (!service.auth) service.auth = $injector.get('egAuth'); + if (!service.auth.token()) return $q.when(); + + // If we have already attempted to retrieve a value for this + // setting, then we can tell up front whether applying a value + // at the server will be an option. If not, store locally. + var summary = service.serverSettingSummaries[key]; + if (summary && !summary.has_staff_setting) { + + if (summary.has_org_setting === 't') { + // When no user/ws setting types exist but an org unit + // setting type does, it means the value cannot be + // applied by an individual user. Nothing left to do. + return $q.when(); + } + + // No setting types of any flavor exist. + // Fall back to local storage. + + if (value === null) { + // a null value means clear the server setting. + return service.removeBrowserItem(key); + } else { + console.warn('No server setting type exists for ' + key); + return service.setBrowserItem(key, value); + } + } + + var settings = {}; + settings[key] = value; + + return egNet.request( + 'open-ils.actor', + 'open-ils.actor.settings.apply.user_or_ws', + service.auth.token(), settings + ).then(function(appliedCount) { + + if (appliedCount == 0) { + console.warn('No server setting type exists for ' + key); + // We were unable to store the setting on the server, + // presumably becuase no server-side setting type exists. + // Add to local storage instead. + service.setLocalItem(key, value); + } + + service.keyCache[key] = value; + return appliedCount; + }); } + service.getServerItem = function(key) { + if (key in service.keyCache) { + return $q.when(service.keyCache[key]) + } + + if (!service.auth) service.auth = $injector.get('egAuth'); + if (!service.auth.token()) return $q.when(null); + + return egNet.request( + 'open-ils.actor', + 'open-ils.actor.settings.retrieve.atomic', + [key], service.auth.token() + ).then(function(settings) { + return service.handleServerItemResponse(settings[0]); + }); + } + + service.handleServerItemResponse = function(summary) { + var key = summary.name; + var val = summary.value; + + // For our purposes, we only care if a setting can be stored + // as an org setting or a user-or-workstation setting. + summary.has_staff_setting = ( + summary.has_user_setting === 't' || + summary.has_workstation_setting === 't' + ); + + summary.value = null; // avoid duplicate value caches + service.serverSettingSummaries[key] = summary; + + if (val !== null) { + // We have a server setting. Nothing left to do. + return $q.when(service.keyCache[key] = val); + } + + if (!summary.has_staff_setting) { + + if (summary.has_org_setting === 't') { + // An org unit setting type exists but no value is applied + // that this workstation has access to. The existence of + // an org unit setting type and no user/ws setting type + // means applying a value locally is not allowed. + return $q.when(service.keyCache[key] = undefined); + } + + console.warn('No server setting type exists for ' + + key + ', using local value.'); + + return service.getBrowserItem(key); + } + + // A user/ws setting type exists, but no server value exists. + // Migrate the local setting to the server. + + var deferred = $q.defer(); + service.getBrowserItem(key).then(function(browserVal) { + + if (browserVal === null || browserVal === undefined) { + // No local value to migrate. + return deferred.resolve(service.keyCache[key] = undefined); + } + + // Migrate the local value to the server. + + service.setServerItem(key, browserVal).then( + function(appliedCount) { + if (appliedCount == 1) { + console.info('setting ' + key + ' successfully ' + + 'migrated to a server setting'); + service.removeBrowserItem(key); // fire & forget + } else { + console.error('error migrating setting to server,' + + ' falling back to local value'); + } + deferred.resolve(service.keyCache[key] = browserVal); + } + ); + }); + + return deferred.promise; + } + + service.getServerItemBatch = function(keys) { + // no cache checking for now. assumes batch mode is only + // called once on page load. maybe add cache checking later. + if (!service.auth) service.auth = $injector.get('egAuth'); + if (!service.auth.token()) return $q.when({}); + + var foundValues = {}; + return egNet.request( + 'open-ils.actor', + 'open-ils.actor.settings.retrieve', + keys, service.auth.token() + ).then( + function() { return foundValues; }, + function() {}, + function(setting) { + var val = setting.value; + // The server returns null for undefined settings. + // Treat as undefined locally for backwards compat. + service.keyCache[setting.name] = + foundValues[setting.name] = + (val === null) ? undefined : val; + } + ); + } + + // set the value for a stored or new item service.setRemoteItem = function(key, value) { service.keyCache[key] = value; @@ -305,8 +576,11 @@ angular.module('egCoreMod') // If the value is raw, pass it as 'value'. If it was // externally JSONified, pass it via jsonified. service.setLocalItem = function(key, value, jsonified) { - if (jsonified === undefined ) + if (jsonified === undefined ) { jsonified = JSON.stringify(value); + } else if (value === undefined) { + return; + } $window.localStorage.setItem(key, jsonified); } @@ -371,21 +645,44 @@ angular.module('egCoreMod') // remove a stored item service.removeItem = function(key) { - if (!service.useSettings()) - return $q.when(service.removeLocalItem(key)); - if (service.hatchAvailable) - return service.removeRemoteItem(key); + if (!service.keyStoredInBrowser(key)) { + return service.removeServerItem(key); + } - if (service.keyIsOnCall(key)) { - console.warn("Unable to removeItem from Hatch: " + key + - ". Removing item from local storage instead"); + var deferred = $q.defer(); + service.removeBrowserItem(key).then( + function(response) {deferred.resolve(response);}, + function() { // Hatch error + + if (service.keyIsOnCall(key)) { + console.warn("Unable to removeItem from Hatch: " + key + + ". Removing item from local storage instead"); + deferred.resolve(service.removeLocalItem(key)); + } + + deferred.reject("Unable to removeItem from Hatch: " + key); + } + ); + + return deferred.promise; + } + + service.removeBrowserItem = function(key) { + if (service.useSettings()) { + if (service.hatchAvailable) { + return service.removeRemoteItem(key); + } else { + return $q.reject('error talking to Hatch'); + } + } else { return $q.when(service.removeLocalItem(key)); } + } - console.error("Unable to removeItem from Hatch: " + key); - return $q.reject(); + service.removeServerItem = function(key) { + return service.setServerItem(key, null); } service.removeRemoteItem = function(key) { @@ -423,9 +720,12 @@ angular.module('egCoreMod') // if set, prefix limits the return set to keys starting with 'prefix' service.getKeys = function(prefix) { - if (service.useSettings()) - return service.getRemoteKeys(prefix); - return $q.when(service.getLocalKeys(prefix)); + var promise = service.getServerKeys(prefix); + return service.getBrowserKeys(prefix).then(function(browserKeys) { + return promise.then(function(serverKeys) { + return serverKeys.concat(browserKeys); + }); + }); } service.getRemoteKeys = function(prefix) { @@ -435,6 +735,22 @@ angular.module('egCoreMod') }); } + service.getBrowserKeys = function(prefix) { + if (service.useSettings()) + return service.getRemoteKeys(prefix); + return $q.when(service.getLocalKeys(prefix)); + } + + service.getServerKeys = function(prefix) { + if (!service.auth) service.auth = $injector.get('egAuth'); + if (!service.auth.token()) return $q.when({}); + return egNet.request( + 'open-ils.actor', + 'open-ils.actor.settings.staff.applied.names.authoritative.atomic', + service.auth.token(), prefix + ); + } + service.getLocalKeys = function(prefix) { var keys = []; var idx = 0; -- 2.11.0