From aaad4807a982edfe968585e6398906dccde4cfde Mon Sep 17 00:00:00 2001 From: Jason Etheridge Date: Fri, 28 May 2021 16:25:22 -0400 Subject: [PATCH] lp1905028 lost items and price versus acq cost This feature adds two new library settings: Use Item Price or Cost as Primary Item Value Use Item Price or Cost as Backup Item Value which intersect the behavior of these existing settings: Charge lost on zero Default Item Price Minimum Item Price Maximum Item Price Each of these settings affect how item price is used in various contexts and is not limited to "lost" items, but can affect notices, fine rules, and billings for long overdue and damaged items (as well as lost items). By default, the price field on items is the only field considered by these various uses, but if we set, for example, "Use Item Price or Cost as Primary Item Value" to "cost", then we'll use the cost field instead of the price field. Alternately, if we set the "Backup Item Value" to "cost" and either leave the "Primary Item Value" setting unset or set to "price", then we'll consider the price field first, and if it is either unset/null or equal to 0 (and "Charge lost on zero" is true), then it'll fall-through to the cost field. We can also flip the behavior with these settings and consider cost first and then price second. Signed-off-by: Jason Etheridge Signed-off-by: Garry Collum Signed-off-by: Mike Rylander --- .../perlmods/lib/OpenILS/Application/AppUtils.pm | 14 +- Open-ILS/src/perlmods/lib/OpenILS/Const.pm | 2 + .../perlmods/live_t/33-lp1905028-price-vs-cost.t | 421 +++++++++++++++++++++ Open-ILS/src/sql/Pg/950.data.seed-values.sql | 40 ++ .../XXXX.data.lp1905028.item_value_fields.sql | 45 +++ docs/RELEASE_NOTES_NEXT/Circulation/acq_price.adoc | 39 ++ 6 files changed, 560 insertions(+), 1 deletion(-) create mode 100644 Open-ILS/src/perlmods/live_t/33-lp1905028-price-vs-cost.t create mode 100644 Open-ILS/src/sql/Pg/upgrade/XXXX.data.lp1905028.item_value_fields.sql create mode 100644 docs/RELEASE_NOTES_NEXT/Circulation/acq_price.adoc diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm index dbf8f70a53..13cdc70799 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/AppUtils.pm @@ -1680,11 +1680,23 @@ sub get_copy_price { my $min_price = $self->ou_ancestor_setting_value($owner, OILS_SETTING_MIN_ITEM_PRICE); my $max_price = $self->ou_ancestor_setting_value($owner, OILS_SETTING_MAX_ITEM_PRICE); my $charge_on_0 = $self->ou_ancestor_setting_value($owner, OILS_SETTING_CHARGE_LOST_ON_ZERO, $e); + my $primary_field = $self->ou_ancestor_setting_value($owner, OILS_SETTING_PRIMARY_ITEM_VALUE_FIELD, $e); + my $backup_field = $self->ou_ancestor_setting_value($owner, OILS_SETTING_SECONDARY_ITEM_VALUE_FIELD, $e); - my $price = $copy->price; + my $price = defined $primary_field && $primary_field eq 'cost' + ? $copy->cost + : $copy->price; # set the default price if needed if (!defined $price or ($price == 0 and $charge_on_0)) { + if (defined $backup_field && $backup_field eq 'cost') { + $price = $copy->cost; + } elsif (defined $backup_field && $backup_field eq 'price') { + $price = $copy->price; + } + } + # possible fallthrough to original default item price behavior + if (!defined $price or ($price == 0 and $charge_on_0)) { # set to default price $price = $self->ou_ancestor_setting_value( $owner, OILS_SETTING_DEF_ITEM_PRICE, $e) || 0; diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Const.pm b/Open-ILS/src/perlmods/lib/OpenILS/Const.pm index f65118efdb..eeb4f8cc0b 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Const.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Const.pm @@ -80,6 +80,8 @@ econst OILS_SETTING_LOST_PROCESSING_FEE => 'circ.lost_materials_processing_fee'; econst OILS_SETTING_DEF_ITEM_PRICE => 'cat.default_item_price'; econst OILS_SETTING_MIN_ITEM_PRICE => 'circ.min_item_price'; econst OILS_SETTING_MAX_ITEM_PRICE => 'circ.max_item_price'; +econst OILS_SETTING_PRIMARY_ITEM_VALUE_FIELD => 'circ.primary_item_value_field'; +econst OILS_SETTING_SECONDARY_ITEM_VALUE_FIELD => 'circ.secondary_item_value_field'; econst OILS_SETTING_ORG_BOUNCED_EMAIL => 'org.bounced_emails'; econst OILS_SETTING_CHARGE_LOST_ON_ZERO => 'circ.charge_lost_on_zero'; econst OILS_SETTING_VOID_OVERDUE_ON_LOST => 'circ.void_overdue_on_lost'; diff --git a/Open-ILS/src/perlmods/live_t/33-lp1905028-price-vs-cost.t b/Open-ILS/src/perlmods/live_t/33-lp1905028-price-vs-cost.t new file mode 100644 index 0000000000..e393c57c6a --- /dev/null +++ b/Open-ILS/src/perlmods/live_t/33-lp1905028-price-vs-cost.t @@ -0,0 +1,421 @@ +#!perl + +use Test::More tests => 55; + +diag('Item price vs cost settings'); + +use constant WORKSTATION_NAME => 'BR1-test-33-lp1905028-price-vs-cost.t'; +use constant WORKSTATION_LIB => 4; + +use strict; use warnings; + +use OpenILS::Utils::TestUtils; +use OpenILS::Utils::CStoreEditor qw/:funcs/; +use OpenILS::Utils::Fieldmapper; +use OpenILS::Const qw(:const); +use OpenILS::Application::AppUtils; +our $U = 'OpenILS::Application::AppUtils'; + +our $script = OpenILS::Utils::TestUtils->new(); +$script->bootstrap; + +our $e = new_editor(xact => 1); +$e->init; + +setupLogin(); + +delete_setting(1, OILS_SETTING_DEF_ITEM_PRICE); +delete_setting(1, OILS_SETTING_MIN_ITEM_PRICE); +delete_setting(1, OILS_SETTING_MAX_ITEM_PRICE); +delete_setting(1, OILS_SETTING_CHARGE_LOST_ON_ZERO); +delete_setting(1, OILS_SETTING_PRIMARY_ITEM_VALUE_FIELD); +delete_setting(1, OILS_SETTING_SECONDARY_ITEM_VALUE_FIELD); +$e->commit; $e = new_editor(xact => 1); $e->init; + +my $def_price; +my $min_price; +my $max_price; +my $charge_on_0; +my $primary_field; +my $backup_field; +fetchSettings(); + +my $price; +my $cost; +my $value; + +my $copy = $e->search_asset_copy([{ id => 404 }, {} ])->[0]; +$copy->clear_price(); +$copy->clear_cost(); +summarize(); +is($value, 0, 'no settings, price = undef, cost = undef, value = 0'); + +$copy->price(0); +$copy->clear_cost(); +summarize(); +is($value, 0, 'no settings, price = 0, cost = undef, value = 0'); + +$copy->price(2); +$copy->clear_cost(); +summarize(); +is($value, 2, 'no settings, price = 2, cost = undef, value = 2'); + +$copy->clear_price(); +$copy->cost(0); +summarize(); +is($value, 0, 'no settings, price = undef, cost = 0, value = 0'); + +$copy->price(0); +$copy->cost(0); +summarize(); +is($value, 0, 'no settings, price = 0, cost = 0, value = 0'); + +$copy->price(2); +$copy->cost(0); +summarize(); +is($value, 2, 'no settings, price = 2, cost = 0, value = 2'); + +$copy->clear_price(); +$copy->cost(3); +summarize(); +is($value, 0, 'no settings, price = undef, cost = 3, value = 0'); + +$copy->price(0); +$copy->cost(0); +summarize(); +is($value, 0, 'no settings, price = 0, cost = 3, value = 0'); + +$copy->price(2); +$copy->cost(0); +summarize(); +is($value, 2, 'no settings, price = 2, cost = 3, value = 2'); + +adjust_setting(1, OILS_SETTING_DEF_ITEM_PRICE, 4); +$e->commit; $e = new_editor(xact => 1); $e->init; +fetchSettings(); + +$copy->clear_price(); +$copy->clear_cost(); +summarize(); +is($value, 4, 'def item price = 4, price = undef, cost = undef, value = 4'); + +$copy->price(0); +$copy->clear_cost(); +summarize(); +is($value, 0, 'def item price = 4, price = 0, cost = undef, value = 0'); + +$copy->price(2); +$copy->clear_cost(); +summarize(); +is($value, 2, 'def item price = 4, price = 2, cost = undef, value = 2'); + +$copy->clear_price(); +$copy->cost(0); +summarize(); +is($value, 4, 'def item price = 4, price = undef, cost = 0, value = 4'); + +$copy->price(0); +$copy->cost(0); +summarize(); +is($value, 0, 'def item price = 4, price = 0, cost = 0, value = 0'); + +$copy->price(2); +$copy->cost(0); +summarize(); +is($value, 2, 'def item price = 4, price = 2, cost = 0, value = 2'); + +$copy->clear_price(); +$copy->cost(3); +summarize(); +is($value, 4, 'def item price = 4, price = undef, cost = 3, value = 4'); + +$copy->price(0); +$copy->cost(3); +summarize(); +is($value, 0, 'def item price = 4, price = 0, cost = 3, value = 0'); + +$copy->price(2); +$copy->cost(3); +summarize(); +is($value, 2, 'def item price = 4, price = 2, cost = 3, value = 2'); + +delete_setting(1, OILS_SETTING_DEF_ITEM_PRICE); +$e->commit; $e = new_editor(xact => 1); $e->init; +adjust_setting(1, OILS_SETTING_PRIMARY_ITEM_VALUE_FIELD, '"cost"'); +$e->commit; $e = new_editor(xact => 1); $e->init; +fetchSettings(); + +$copy->clear_price(); +$copy->clear_cost(); +summarize(); +is($value, 0, 'primary = cost, price = undef, cost = undef, value = 0'); + +$copy->price(0); +$copy->clear_cost(); +summarize(); +is($value, 0, 'primary = cost, price = 0, cost = undef, value = 0'); + +$copy->price(2); +$copy->clear_cost(); +summarize(); +is($value, 0, 'primary = cost, price = 2, cost = undef, value = 0'); + +$copy->clear_price(); +$copy->cost(0); +summarize(); +is($value, 0, 'primary = cost, price = undef, cost = 0, value = 0'); + +$copy->price(0); +$copy->cost(0); +summarize(); +is($value, 0, 'primary = cost, price = 0, cost = 0, value = 0'); + +$copy->price(2); +$copy->cost(0); +summarize(); +is($value, 0, 'primary = cost, price = 2, cost = 0, value = 0'); + +$copy->clear_price(); +$copy->cost(3); +summarize(); +is($value, 3, 'primary = cost, price = undef, cost = 3, value = 3'); + +$copy->price(0); +$copy->cost(3); +summarize(); +is($value, 3, 'primary = cost, price = 0, cost = 3, value = 3'); + +$copy->price(2); +$copy->cost(3); +summarize(); +is($value, 3, 'primary = cost, price = 2, cost = 3, value = 3'); + +adjust_setting(1, OILS_SETTING_DEF_ITEM_PRICE, 4); +$e->commit; $e = new_editor(xact => 1); $e->init; +adjust_setting(1, OILS_SETTING_PRIMARY_ITEM_VALUE_FIELD, '"cost"'); +$e->commit; $e = new_editor(xact => 1); $e->init; +fetchSettings(); + +$copy->clear_price(); +$copy->clear_cost(); +summarize(); +is($value, 4, 'def item price = 4, primary = cost, price = undef, cost = undef, value = 4'); + +$copy->price(0); +$copy->clear_cost(); +summarize(); +is($value, 4, 'def item price = 4, primary = cost, price = 0, cost = undef, value = 4'); + +$copy->price(2); +$copy->clear_cost(); +summarize(); +is($value, 4, 'def item price = 4, primary = cost, price = 2, cost = undef, value = 4'); + +$copy->clear_price(); +$copy->cost(0); +summarize(); +is($value, 0, 'def item price = 4, primary = cost, price = undef, cost = 0, value = 0'); + +$copy->price(0); +$copy->cost(0); +summarize(); +is($value, 0, 'def item price = 4, primary = cost, price = 0, cost = 0, value = 0'); + +$copy->price(2); +$copy->cost(0); +summarize(); +is($value, 0, 'def item price = 4, primary = cost, price = 2, cost = 0, value = 0'); + +$copy->clear_price(); +$copy->cost(3); +summarize(); +is($value, 3, 'def item price = 4, primary = cost, price = undef, cost = 3, value = 3'); + +$copy->price(0); +$copy->cost(3); +summarize(); +is($value, 3, 'def item price = 4, primary = cost, price = 0, cost = 3, value = 3'); + +$copy->price(2); +$copy->cost(3); +summarize(); +is($value, 3, 'def item price = 4, primary = cost, price = 2, cost = 3, value = 3'); + +adjust_setting(1, OILS_SETTING_DEF_ITEM_PRICE, 4); +$e->commit; $e = new_editor(xact => 1); $e->init; +adjust_setting(1, OILS_SETTING_PRIMARY_ITEM_VALUE_FIELD, '"cost"'); +$e->commit; $e = new_editor(xact => 1); $e->init; +adjust_setting(1, OILS_SETTING_SECONDARY_ITEM_VALUE_FIELD, '"price"'); +$e->commit; $e = new_editor(xact => 1); $e->init; +fetchSettings(); + +$copy->clear_price(); +$copy->clear_cost(); +summarize(); +is($value, 4, 'def item price = 4, primary = cost, secondary = price, price = undef, cost = undef, value = 4'); + +$copy->price(0); +$copy->clear_cost(); +summarize(); +is($value, 0, 'def item price = 4, primary = cost, secondary = price, price = 0, cost = undef, value = 0'); + +$copy->price(2); +$copy->clear_cost(); +summarize(); +is($value, 2, 'def item price = 4, primary = cost, secondary = price, price = 2, cost = undef, value = 2'); + +$copy->clear_price(); +$copy->cost(0); +summarize(); +is($value, 0, 'def item price = 4, primary = cost, secondary = price, price = undef, cost = 0, value = 0'); + +$copy->price(0); +$copy->cost(0); +summarize(); +is($value, 0, 'def item price = 4, primary = cost, secondary = price, price = 0, cost = 0, value = 0'); + +$copy->price(2); +$copy->cost(0); +summarize(); +is($value, 0, 'def item price = 4, primary = cost, secondary = price, price = 2, cost = 0, value = 0'); + +$copy->clear_price(); +$copy->cost(3); +summarize(); +is($value, 3, 'def item price = 4, primary = cost, secondary = price, price = undef, cost = 3, value = 3'); + +$copy->price(0); +$copy->cost(3); +summarize(); +is($value, 3, 'def item price = 4, primary = cost, secondary = price, price = 0, cost = 3, value = 3'); + +$copy->price(2); +$copy->cost(3); +summarize(); +is($value, 3, 'def item price = 4, primary = cost, secondary = price, price = 2, cost = 3, value = 3'); + +adjust_setting(1, OILS_SETTING_DEF_ITEM_PRICE, 4); +$e->commit; $e = new_editor(xact => 1); $e->init; +adjust_setting(1, OILS_SETTING_PRIMARY_ITEM_VALUE_FIELD, '"cost"'); +$e->commit; $e = new_editor(xact => 1); $e->init; +adjust_setting(1, OILS_SETTING_SECONDARY_ITEM_VALUE_FIELD, '"price"'); +$e->commit; $e = new_editor(xact => 1); $e->init; +adjust_setting(1, OILS_SETTING_CHARGE_LOST_ON_ZERO, '"true"'); +$e->commit; $e = new_editor(xact => 1); $e->init; +fetchSettings(); + +$copy->clear_price(); +$copy->clear_cost(); +summarize(); +is($value, 4, 'charge_on_zero = true, def item price = 4, primary = cost, secondary = price, price = undef, cost = undef, value = 4'); + +$copy->price(0); +$copy->clear_cost(); +summarize(); +is($value, 4, 'charge_on_zero = true, def item price = 4, primary = cost, secondary = price, price = 0, cost = undef, value = 4'); + +$copy->price(2); +$copy->clear_cost(); +summarize(); +is($value, 2, 'charge_on_zero = true, def item price = 4, primary = cost, secondary = price, price = 2, cost = undef, value = 2'); + +$copy->clear_price(); +$copy->cost(0); +summarize(); +is($value, 4, 'charge_on_zero = true, def item price = 4, primary = cost, secondary = price, price = undef, cost = 0, value = 4'); + +$copy->price(0); +$copy->cost(0); +summarize(); +is($value, 4, 'charge_on_zero = true, def item price = 4, primary = cost, secondary = price, price = 0, cost = 0, value = 4'); + +$copy->price(2); +$copy->cost(0); +summarize(); +is($value, 2, 'charge_on_zero = true, def item price = 4, primary = cost, secondary = price, price = 2, cost = 0, value = 2'); + +$copy->clear_price(); +$copy->cost(3); +summarize(); +is($value, 3, 'charge_on_zero = true, def item price = 4, primary = cost, secondary = price, price = undef, cost = 3, value = 3'); + +$copy->price(0); +$copy->cost(3); +summarize(); +is($value, 3, 'charge_on_zero = true, def item price = 4, primary = cost, secondary = price, price = 0, cost = 3, value = 3'); + +$copy->price(2); +$copy->cost(3); +summarize(); +is($value, 3, 'charge_on_zero = true, def item price = 4, primary = cost, secondary = price, price = 2, cost = 3, value = 3'); + + +#################### + +sub delete_setting { + my ($org, $setting) = (shift, shift); + my $obj = $e->search_actor_org_unit_setting([{ org_unit => $org, name => $setting }, {} ])->[0]; + if (defined $obj) { + $e->delete_actor_org_unit_setting($obj); + } +} + +sub adjust_setting { + my ($org, $setting, $value) = (shift, shift, shift); + my $obj = $e->search_actor_org_unit_setting([{ org_unit => $org, name => $setting }, {} ])->[0]; + my $update = defined $obj; + $obj = Fieldmapper::actor::org_unit_setting->new unless $update; + $obj->org_unit($org); + $obj->name($setting); + $obj->value($value); + return $update ? $e->update_actor_org_unit_setting($obj) : $e->create_actor_org_unit_setting($obj); +} + +sub fetchSettings { + $def_price = $U->ou_ancestor_setting_value(1, OILS_SETTING_DEF_ITEM_PRICE, $e); + $min_price = $U->ou_ancestor_setting_value(1, OILS_SETTING_MIN_ITEM_PRICE, $e); + $max_price = $U->ou_ancestor_setting_value(1, OILS_SETTING_MAX_ITEM_PRICE, $e); + $charge_on_0 = $U->ou_ancestor_setting_value(1, OILS_SETTING_CHARGE_LOST_ON_ZERO, $e); + $primary_field = $U->ou_ancestor_setting_value(1, OILS_SETTING_PRIMARY_ITEM_VALUE_FIELD, $e); + $backup_field = $U->ou_ancestor_setting_value(1, OILS_SETTING_SECONDARY_ITEM_VALUE_FIELD, $e); + $def_price = defined $def_price ? $def_price : ''; + $min_price = defined $min_price ? $min_price : ''; + $max_price = defined $max_price ? $max_price : ''; + $charge_on_0 = defined $charge_on_0 ? $charge_on_0 : ''; + $primary_field = defined $primary_field ? $primary_field : ''; + $backup_field = defined $backup_field ? $backup_field : ''; + diag("def_price = $def_price charge_on_0 = $charge_on_0 primary_field = $primary_field backup_field = $backup_field"); +} + +sub summarize { + $value = $U->get_copy_price($e, $copy, $copy->call_number); + $value = length $value ? $value : ''; + $price = length $copy->price ? $copy->price : ''; + $cost = length $copy->cost ? $copy->cost : ''; + #diag("Using settings -> def_price: $def_price min_price: $min_price max_price: $max_price charge_on_0: $charge_on_0 primary: $primary_field backup: $backup_field"); + #diag("Using copy " . $copy->id . " -> price: $price cost: $cost value: $value"); +} + +sub setupLogin { + + my $workstation = $e->search_actor_workstation([ {name => WORKSTATION_NAME, owning_lib => WORKSTATION_LIB } ])->[0]; + + if(!$workstation ) + { + $script->authenticate({ + username => 'admin', + password => 'demo123', + type => 'staff'}); + ok( $script->authtoken, 'Have an authtoken'); + my $ws = $script->register_workstation(WORKSTATION_NAME,WORKSTATION_LIB); + ok( ! ref $ws, 'Registered a new workstation'); + $script->logout(); + } + + $script->authenticate({ + username => 'admin', + password => 'demo123', + type => 'staff', + workstation => WORKSTATION_NAME}); + ok( $script->authtoken, 'Have an authtoken associated with the workstation'); +} 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 7b27f5db2f..4d5c730293 100644 --- a/Open-ILS/src/sql/Pg/950.data.seed-values.sql +++ b/Open-ILS/src/sql/Pg/950.data.seed-values.sql @@ -21822,3 +21822,43 @@ VALUES ( ) ); +INSERT into config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES ( + 'circ.primary_item_value_field', + 'circ', + oils_i18n_gettext( + 'circ.primary_item_value_field', + 'Use Item Price or Cost as Primary Item Value', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.primary_item_value_field', + 'Expects "price" or "cost" and defaults to price. This refers to the corresponding field on the item record and gets used in such contexts as notices, max fine values when using item price caps (setting or fine rules), and long overdue, damaged, and lost billings.', + 'coust', + 'description' + ), + 'string' + ); + +INSERT into config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES ( + 'circ.secondary_item_value_field', + 'circ', + oils_i18n_gettext( + 'circ.secondary_item_value_field', + 'Use Item Price or Cost as Backup Item Value', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.secondary_item_value_field', + 'Expects "price" or "cost", but defaults to neither. This refers to the corresponding field on the item record and is used as a second-pass fall-through value when determining an item value. If needed, Evergreen will still look at the "Default Item Price" setting as a final fallback.', + 'coust', + 'description' + ), + 'string' + ); + diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.data.lp1905028.item_value_fields.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.lp1905028.item_value_fields.sql new file mode 100644 index 0000000000..b27de609e6 --- /dev/null +++ b/Open-ILS/src/sql/Pg/upgrade/XXXX.data.lp1905028.item_value_fields.sql @@ -0,0 +1,45 @@ +BEGIN; + +SELECT evergreen.upgrade_deps_block_check('XXXX', :eg_version); + +INSERT into config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES ( + 'circ.primary_item_value_field', + 'circ', + oils_i18n_gettext( + 'circ.primary_item_value_field', + 'Use Item Price or Cost as Primary Item Value', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.primary_item_value_field', + 'Expects "price" or "cost" and defaults to price. This refers to the corresponding field on the item record and gets used in such contexts as notices, max fine values when using item price caps (setting or fine rules), and long overdue, damaged, and lost billings.', + 'coust', + 'description' + ), + 'string' + ); + +INSERT into config.org_unit_setting_type + (name, grp, label, description, datatype) + VALUES ( + 'circ.secondary_item_value_field', + 'circ', + oils_i18n_gettext( + 'circ.secondary_item_value_field', + 'Use Item Price or Cost as Backup Item Value', + 'coust', + 'label' + ), + oils_i18n_gettext( + 'circ.secondary_item_value_field', + 'Expects "price" or "cost", but defaults to neither. This refers to the corresponding field on the item record and is used as a second-pass fall-through value when determining an item value. If needed, Evergreen will still look at the "Default Item Price" setting as a final fallback.', + 'coust', + 'description' + ), + 'string' + ); + +COMMIT; diff --git a/docs/RELEASE_NOTES_NEXT/Circulation/acq_price.adoc b/docs/RELEASE_NOTES_NEXT/Circulation/acq_price.adoc new file mode 100644 index 0000000000..ef6c961dfd --- /dev/null +++ b/docs/RELEASE_NOTES_NEXT/Circulation/acq_price.adoc @@ -0,0 +1,39 @@ +== Granular control over how to use price and acquisition cost to determine item value == + +This feature adds two new library settings: + + Use Item Price or Cost as Primary Item Value + Use Item Price or Cost as Backup Item Value + +which intersect the behavior of these existing settings: + + Charge lost on zero + Default Item Price + Minimum Item Price + Maximum Item Price + +Each of these settings affect how item price is used in +various contexts and is not limited to "lost" items, but +can affect notices, fine rules, and billings for long +overdue and damaged items (as well as lost items). + +By default, the price field on items is the only field +considered by these various uses, but if we set, for +example, "Use Item Price or Cost as Primary Item Value" to +"cost", then we'll use the cost field instead of the price +field. + +Alternately, if we set the "Backup Item Value" to "cost" +and either leave the "Primary Item Value" setting unset or +set to "price", then we'll consider the price field first, +and if it is either unset/null or equal to 0 (and +"Charge lost on zero" is true), then it'll fall-through to +the cost field. We can also flip the behavior with these +settings and consider cost first and then price second. + +The primary intended use case for this feature is: + + If there's an acquisition cost, charge this as the lost value. + If there's not an acquisition cost, but there's price, charge the price. + If neither, charge the default value. + -- 2.11.0