diff --git a/app/controllers/case_distribution_levers_controller.rb b/app/controllers/case_distribution_levers_controller.rb index 22f57b540d0..7cc6cdada3d 100644 --- a/app/controllers/case_distribution_levers_controller.rb +++ b/app/controllers/case_distribution_levers_controller.rb @@ -46,7 +46,7 @@ def authorize_admin end def allowed_params - params.permit(current_levers: [:id, :value]) + params.permit(current_levers: [:id, :value, :is_toggle_active]) end def verify_access diff --git a/app/controllers/judge_assign_tasks_controller.rb b/app/controllers/judge_assign_tasks_controller.rb index 928cae44589..04dd49ad349 100644 --- a/app/controllers/judge_assign_tasks_controller.rb +++ b/app/controllers/judge_assign_tasks_controller.rb @@ -11,6 +11,10 @@ def create tasks_to_return = (tasks + queue_for_role.tasks).uniq render json: { tasks: json_tasks(tasks_to_return) } + rescue ActiveRecord::RecordInvalid => error + invalid_record_error(error.record) + rescue Caseflow::Error::MailRoutingError => error + render(error.serialize_response) end private diff --git a/app/models/case_distribution_lever.rb b/app/models/case_distribution_lever.rb index c168c1089b5..9ca22443ae8 100644 --- a/app/models/case_distribution_lever.rb +++ b/app/models/case_distribution_lever.rb @@ -5,7 +5,7 @@ class CaseDistributionLever < ApplicationRecord validates :item, presence: true validates :title, presence: true validates :data_type, presence: true, inclusion: { in: Constants.ACD_LEVERS.data_types.to_h.values } - validates :is_toggle_active, inclusion: { in: [true, false] } + validates :is_toggle_active, inclusion: { in: [true, false, nil] } validates :is_disabled_in_ui, inclusion: { in: [true, false] } validate :value_matches_data_type @@ -28,15 +28,24 @@ class CaseDistributionLever < ApplicationRecord #{Constants.DISTRIBUTION.nod_adjustment} ).freeze - def distribution_value - if data_type == Constants.ACD_LEVERS.data_types.radio - option = options.detect { |opt| opt["item"] == value } - option["value"] if option&.is_a?(Hash) + def history_value + if combination_lever + combination_value + elsif radio_lever + radio_value else value end end + def combination_lever + data_type == Constants.ACD_LEVERS.data_types.combination + end + + def radio_lever + data_type == Constants.ACD_LEVERS.data_types.radio + end + private def value_matches_data_type @@ -48,7 +57,7 @@ def value_matches_data_type when Constants.ACD_LEVERS.data_types.boolean validate_boolean_data_type when Constants.ACD_LEVERS.data_types.combination - validate_options + validate_combination_data_type end end @@ -71,6 +80,32 @@ def validate_boolean_data_type add_error_value_not_match_data_type if value&.match(/\A(t|true|f|false)\z/i).nil? end + def validate_combination_data_type + errors.add(:item, "is of #{data_type} and does not contain a valid is_toggle_active value") if is_toggle_active.nil? + validate_options + end + + # this matches what is displayed in frontend + # see client/app/caseDistribution/components/SaveModal.jsx + def combination_value + toggle_string = is_toggle_active ? "Active" : "Inactive" + "#{toggle_string} - #{value}" + end + + def option(item) + options&.find{ |option| option["item"] == item } || {} + end + + # this matches what is displayed in frontend + # see client/app/caseDistribution/components/SaveModal.jsx + def radio_value + return option(value)["text"] if [Constants.ACD_LEVERS.omit, Constants.ACD_LEVERS.infinite].include?(value.to_s) + + selected_option = option(Constants.ACD_LEVERS.value) + + "#{selected_option["text"]} #{value.to_s} #{selected_option["unit"]}" + end + class << self def respond_to_missing?(name, _include_private) Constants.DISTRIBUTION.to_h.key?(name) @@ -91,6 +126,15 @@ def update_acd_levers(current_levers, current_user) errors = [] levers = [] + # if lever is a radio update options object + grouped_levers.each_pair do |lever_id, lever| + previous_lever = previous_levers[lever_id] + next unless previous_lever.radio_lever + + # update options + update_radio_options(lever, previous_lever.options) + end + ActiveRecord::Base.transaction do levers = CaseDistributionLever.update(grouped_levers.keys, grouped_levers.values) @@ -105,7 +149,7 @@ def update_acd_levers(current_levers, current_user) private def method_missing_value(name) - lever = find_by_item(name).try(:distribution_value) + lever = find_by_item(name).try(:value) if INTEGER_LEVERS.include?(name) lever.to_i @@ -123,8 +167,8 @@ def add_audit_lever_entries(previous_levers, levers, current_user) entries.push({ user: current_user, case_distribution_lever: lever, - previous_value: previous_lever.value, - update_value: lever.value + previous_value: previous_lever.history_value, + update_value: lever.history_value }) end @@ -138,5 +182,25 @@ def add_audit_lever_entries(previous_levers, levers, current_user) [] end + + # Modified by reference the lever and options objects and then add + # lever["options"] so that CaseDistributionLever.update updates the options field + def update_radio_options(lever, options) + selected_option = if [Constants.ACD_LEVERS.omit, Constants.ACD_LEVERS.infinite].include?(lever["value"]) + lever["value"] + else + Constants.ACD_LEVERS.value + end + + options.each do |option| + option["selected"] = option["item"] == selected_option + + if option["selected"] && option["item"] == Constants.ACD_LEVERS.value + option["value"] = lever["value"].to_i + end + end + + lever["options"] = options + end end end diff --git a/app/models/tasks/attorney_task.rb b/app/models/tasks/attorney_task.rb index 040fe5c71fb..6c22c745fcd 100644 --- a/app/models/tasks/attorney_task.rb +++ b/app/models/tasks/attorney_task.rb @@ -105,8 +105,9 @@ def self_assigned?(user) def assigned_to_role_is_valid is_self = assigned_to == assigned_by + errored = assigned_to && !assigned_to.attorney_in_vacols? && !is_self - errors.add(:assigned_to, "has to be an attorney") if assigned_to && !assigned_to.attorney_in_vacols? && !is_self + errors.add(:base, "The selected individual is not an attorney in VACOLS") if errored end def assigned_by_role_is_valid diff --git a/client/COPY.json b/client/COPY.json index b37d3391eba..fc5f8584f49 100644 --- a/client/COPY.json +++ b/client/COPY.json @@ -456,7 +456,7 @@ "ASSIGN_WIDGET_NO_TASK_DETAIL": "Please select a task.", "ASSIGN_WIDGET_SUCCESS": "%(verb)s %(numCases)s %(casePlural)s to %(assignee)s", "ASSIGN_WIDGET_ASSIGNMENT_ERROR_TITLE": "Error assigning tasks", - "ASSIGN_WIDGET_ASSIGNMENT_ERROR_DETAIL": "Timeout Error while assigning tasks; please reload the page before proceeding.", + "ASSIGN_WIDGET_ASSIGNMENT_ERROR_DETAIL": "Error occurred while assigning tasks. You may need to reload the page before proceeding.", "ASSIGN_WIDGET_ASSIGNMENT_ERROR_DETAIL_MODAL_LINK": "Please assign tasks to an attorney from your assign page.", "ASSIGN_WIDGET_ASSIGNMENT_ERROR_DETAIL_MODAL": " Reassign tasks to a judge in the action dropdown", "ASSIGN_WIDGET_LOADING": "Loading...", diff --git a/client/app/caseDistribution/components/AffinityDays.jsx b/client/app/caseDistribution/components/AffinityDays.jsx index facda2c7192..06beeafa00f 100644 --- a/client/app/caseDistribution/components/AffinityDays.jsx +++ b/client/app/caseDistribution/components/AffinityDays.jsx @@ -1,28 +1,51 @@ import React, { useState, useEffect } from 'react'; -import { useSelector } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import cx from 'classnames'; import NumberField from 'app/components/NumberField'; import TextField from 'app/components/TextField'; import COPY from '../../../COPY'; import ACD_LEVERS from '../../../constants/ACD_LEVERS'; -import { getUserIsAcdAdmin, getLeversByGroup } from '../reducers/levers/leversSelector'; +import { getUserIsAcdAdmin, getLeversByGroup, getLeverErrors } from '../reducers/levers/leversSelector'; import { Constant } from '../constants'; import { dynamicallyAddAsterisk } from '../utils'; +import { validateLever, updateRadioLever } from '../reducers/levers/leversActions'; const AffinityDays = () => { const theState = useSelector((state) => state); + const dispatch = useDispatch(); const isUserAcdAdmin = getUserIsAcdAdmin(theState); - const storeLevers = getLeversByGroup(theState, Constant.LEVERS, ACD_LEVERS.lever_groups.affinity); const [affinityLevers, setAffinityLevers] = useState(storeLevers); + const leverErrors = (leverItem) => { + return getLeverErrors(theState, leverItem); + }; + useEffect(() => { setAffinityLevers(storeLevers); }, [storeLevers]); + const isOptionSelected = (lever, option) => lever.selectedOption === option.item; + + const onChangeRadio = (lever, option) => () => { + // eslint-disable-next-line camelcase + const { lever_group, item } = lever; + + dispatch(updateRadioLever(lever_group, item, option.item, option.value)); + }; + + const onChangeField = (lever, option) => (event) => { + // eslint-disable-next-line camelcase + const { lever_group, item } = lever; + + dispatch(validateLever(lever, item, event, leverErrors(item))); + dispatch(updateRadioLever(lever_group, item, option.item, event)); + }; + const generateFields = (dataType, option, lever) => { const useAriaLabel = !lever.is_disabled_in_ui; const tabIndex = lever.is_disabled_in_ui ? -1 : 0; + const value = lever.valueOptionValue; if (dataType === ACD_LEVERS.data_types.number) { return ( @@ -31,12 +54,12 @@ const AffinityDays = () => { title={option.text} label={option.unit} isInteger - readOnly={lever.is_disabled_in_ui ? true : (lever.value !== option.item)} - value={option.value} - errorMessage={option.errorMessage} - onChange={() => console.warn('not implemented')} - id={`${lever.item}-${option.value}`} - inputID={`${lever.item}-${option.value}-input`} + readOnly={lever.is_disabled_in_ui ? true : !isOptionSelected(lever, option)} + value={value} + errorMessage={leverErrors(lever.item)} + onChange={onChangeField(lever, option)} + id={`${lever.item}-${value}`} + inputID={`${lever.item}-${value}-input`} useAriaLabel={useAriaLabel} tabIndex={tabIndex} disabled={lever.is_disabled_in_ui} @@ -49,11 +72,12 @@ const AffinityDays = () => { name={option.item} title={option.text} label={false} - readOnly={lever.is_disabled_in_ui ? true : (lever.value !== option.item)} - value={option.value} - onChange={() => console.warn('not implemented')} - id={`${lever.item}-${option.value}`} - inputID={`${lever.item}-${option.value}-input`} + readOnly={lever.is_disabled_in_ui ? true : !isOptionSelected(lever, option)} + value={value} + errorMessage={leverErrors(lever.item)} + onChange={onChangeField(lever, option)} + id={`${lever.item}-${value}`} + inputID={`${lever.item}-${value}-input`} useAriaLabel={useAriaLabel} tabIndex={tabIndex} disabled={lever.is_disabled_in_ui} @@ -67,7 +91,7 @@ const AffinityDays = () => { const generateMemberViewLabel = (option, lever, index) => { const affinityLabelId = `affinity-day-label-for-${lever.item}`; - if (lever.value === option.item) { + if (isOptionSelected(lever, option)) { return (
@@ -76,7 +100,7 @@ const AffinityDays = () => { htmlFor={`${lever.item}-${option.item}`} > {`${option.text} ${option.data_type === ACD_LEVERS.data_types.number ? - `${option.value} ${option.unit}` : ''}`} + `${lever.value} ${option.unit}` : ''}`}
@@ -87,19 +111,19 @@ const AffinityDays = () => { }; const renderAdminInput = (option, lever, index) => { - const className = cx('combined-radio-input', (lever.value === option.item) ? '' : 'outline-radio-input'); + const className = cx('combined-radio-input', (isOptionSelected(lever, option)) ? '' : 'outline-radio-input'); return (
console.warn('not implemented')} + onChange={onChangeRadio(lever, option)} />
- {generateFields(option.data_type, option, lever, isUserAcdAdmin)} + {generateFields(option.data_type, option, lever)}
diff --git a/client/app/caseDistribution/components/BatchSize.jsx b/client/app/caseDistribution/components/BatchSize.jsx index bdfc5fb98a2..13ebc875fbc 100644 --- a/client/app/caseDistribution/components/BatchSize.jsx +++ b/client/app/caseDistribution/components/BatchSize.jsx @@ -4,7 +4,7 @@ import cx from 'classnames'; import NumberField from 'app/components/NumberField'; import COPY from '../../../COPY'; import { getLeversByGroup, getLeverErrors, getUserIsAcdAdmin } from '../reducers/levers/leversSelector'; -import { updateNumberLever, validateLever } from '../reducers/levers/leversActions'; +import { updateLeverValue, validateLever } from '../reducers/levers/leversActions'; import { Constant } from '../constants'; import ACD_LEVERS from '../../../constants/ACD_LEVERS'; import { dynamicallyAddAsterisk } from '../utils'; @@ -30,7 +30,7 @@ const BatchSize = () => { const { lever_group, item } = lever; dispatch(validateLever(lever, item, event, leverErrors(item))); - dispatch(updateNumberLever(lever_group, item, event)); + dispatch(updateLeverValue(lever_group, item, event)); }; return ( diff --git a/client/app/caseDistribution/components/DocketTimeGoals.jsx b/client/app/caseDistribution/components/DocketTimeGoals.jsx index 4269e85ed8b..520a25732c1 100644 --- a/client/app/caseDistribution/components/DocketTimeGoals.jsx +++ b/client/app/caseDistribution/components/DocketTimeGoals.jsx @@ -1,7 +1,11 @@ import React, { useState, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import cx from 'classnames'; -import { updateNumberLever, validateLever } from '../reducers/levers/leversActions'; +import { + updateLeverValue, + validateLever, + updateLeverIsToggleActive +} from '../reducers/levers/leversActions'; import ToggleSwitch from 'app/components/ToggleSwitch/ToggleSwitch'; import NumberField from 'app/components/NumberField'; import COPY from '../../../COPY'; @@ -42,22 +46,17 @@ const DocketTimeGoals = () => { const { lever_group, item } = lever; dispatch(validateLever(lever, item, event, leverErrors(item))); - dispatch(updateNumberLever(lever_group, item, event)); + dispatch(updateLeverValue(lever_group, item, event)); }; - const toggleLever = (index) => () => { - const levers = docketDistributionLevers.map((lever, i) => { - if (index === i) { - lever.is_toggle_active = !lever.is_toggle_active; + const toggleLever = (lever) => () => { + const { + lever_group: leverGroup, + item, + is_toggle_active: isToggleActive + } = lever; - return lever; - } - - return lever; - - }); - - setDistributionLever(levers); + dispatch(updateLeverIsToggleActive(leverGroup, item, !isToggleActive)); }; const renderDocketDistributionLever = (distributionPriorLever, index) => { @@ -101,7 +100,7 @@ const DocketTimeGoals = () => { id={`toggle-switch-${distributionPriorLever.item}`} selected={distributionPriorLever.is_toggle_active} disabled={distributionPriorLever.is_disabled_in_ui} - toggleSelected={toggleLever(index)} + toggleSelected={toggleLever(distributionPriorLever)} />
{ @@ -15,18 +15,49 @@ export const SaveModal = (props) => { const theState = useSelector((state) => state); - const leverValueDisplay = (lever) => { - const doesDatatypeRequireComplexLogic = (lever.data_type === ACD_LEVERS.data_types.radio || - lever.data_type === ACD_LEVERS.data_types.combination); + const combinationValue = (value, isToggleActive) => { + const toggleString = isToggleActive ? 'Active' : 'Inactive'; + + return `${toggleString} - ${value}`; + }; + + /** + * If omit or infinite + * Return the text for the option + * + * If value + * Return the text, value, and unit for the option + */ + const radioValue = (lever, value) => { + if ([ACD_LEVERS.omit, ACD_LEVERS.infinite].includes(value)) { + return findOption(lever, value).text; + } + + const selectedOption = findValueOption(lever); + + return `${selectedOption.text} ${value} ${selectedOption.unit}`; + }; - if (doesDatatypeRequireComplexLogic) { - const selectedOption = findOption(lever, lever.value); - const isSelectedOptionANumber = selectedOption.data_type === ACD_LEVERS.data_types.number; + const changedLeverDisplayValue = (lever, value, isToggleActive) => { + let displayValue = value; - return isSelectedOptionANumber ? selectedOption.value : selectedOption.text; + if (lever.data_type === ACD_LEVERS.data_types.radio) { + displayValue = radioValue(lever, value); } - return {lever.value}; + if (lever.data_type === ACD_LEVERS.data_types.combination) { + displayValue = combinationValue(value, isToggleActive); + } + + return displayValue; + }; + + const backendValueDisplay = (lever) => { + return <>{changedLeverDisplayValue(lever, lever.backendValue, lever.backendIsToggleActive)}; + }; + + const leverValueDisplay = (lever) => { + return {changedLeverDisplayValue(lever, lever.value, lever.is_toggle_active)}; }; const leverList = () => { @@ -62,7 +93,7 @@ export const SaveModal = (props) => { id={`${lever.item}-previous-value`} className={cx('modal-table-styling', 'modal-table-right-styling')} > - {lever.backendValue} + {backendValueDisplay(lever)} +export const updateLeverIsToggleActive = (leverGroup, leverItem, toggleValue) => (dispatch) => { dispatch({ - type: ACTIONS.UPDATE_COMBINATION_LEVER, + type: ACTIONS.UPDATE_LEVER_IS_TOGGLE_ACTIVE, payload: { leverGroup, leverItem, - value, toggleValue } }); }; -export const updateBooleanLever = (leverGroup, leverItem, value) => - (dispatch) => { - dispatch({ - type: ACTIONS.UPDATE_BOOLEAN_LEVER, - payload: { - leverGroup, - leverItem, - value - } - }); - }; - -export const updateTextLever = (leverGroup, leverItem, value) => - (dispatch) => { - dispatch({ - type: ACTIONS.UPDATE_TEXT_LEVER, - payload: { - leverGroup, - leverItem, - value - } - }); - }; - -export const updateNumberLever = (leverGroup, leverItem, value) => +export const updateLeverValue = (leverGroup, leverItem, value) => (dispatch) => { dispatch({ - type: ACTIONS.UPDATE_NUMBER_LEVER, + type: ACTIONS.UPDATE_LEVER_VALUE, payload: { leverGroup, leverItem, @@ -108,10 +83,11 @@ export const updateNumberLever = (leverGroup, leverItem, value) => export const saveLevers = (levers) => (dispatch) => { - const changedValues = Object.values(levers).flat(). + const changedValues = levers. map((lever) => ({ id: lever.id, - value: lever.value + value: lever.value, + is_toggle_active: lever.is_toggle_active })); const postData = { diff --git a/client/app/caseDistribution/reducers/levers/leversReducer.js b/client/app/caseDistribution/reducers/levers/leversReducer.js index 62c032e4293..73222a4fee1 100644 --- a/client/app/caseDistribution/reducers/levers/leversReducer.js +++ b/client/app/caseDistribution/reducers/levers/leversReducer.js @@ -1,9 +1,9 @@ import { ACTIONS } from '../levers/leversActionTypes'; import { update } from '../../../util/ReducerUtil'; import { - createUpdatedLever, - createUpdatedRadioLever, - createUpdatedCombinationLever + updateLeverGroupForValue, + updateLeverGroupForRadioLever, + updateLeverGroupForIsToggleActive } from './leversSelector'; import { createUpdatedLeversWithValues, @@ -41,10 +41,8 @@ const leversReducer = (state = initialState, action = {}) => { $set: action.payload.isUserAcdAdmin } }); - case ACTIONS.UPDATE_BOOLEAN_LEVER: - case ACTIONS.UPDATE_NUMBER_LEVER: - case ACTIONS.UPDATE_TEXT_LEVER: { - const leverGroup = createUpdatedLever(state, action); + case ACTIONS.UPDATE_LEVER_VALUE: { + const leverGroup = updateLeverGroupForValue(state, action); return { ...state, @@ -54,8 +52,8 @@ const leversReducer = (state = initialState, action = {}) => { }, }; } - case ACTIONS.UPDATE_COMBINATION_LEVER: { - const leverGroup = createUpdatedCombinationLever(state, action); + case ACTIONS.UPDATE_LEVER_IS_TOGGLE_ACTIVE: { + const leverGroup = updateLeverGroupForIsToggleActive(state, action); return { ...state, @@ -66,7 +64,7 @@ const leversReducer = (state = initialState, action = {}) => { }; } case ACTIONS.UPDATE_RADIO_LEVER: { - const leverGroup = createUpdatedRadioLever(state, action); + const leverGroup = updateLeverGroupForRadioLever(state, action); return { ...state, diff --git a/client/app/caseDistribution/reducers/levers/leversSelector.js b/client/app/caseDistribution/reducers/levers/leversSelector.js index 2253fa26bc0..3c710b2e56d 100644 --- a/client/app/caseDistribution/reducers/levers/leversSelector.js +++ b/client/app/caseDistribution/reducers/levers/leversSelector.js @@ -1,6 +1,12 @@ import { createSelector } from 'reselect'; import ACD_LEVERS from '../../../../constants/ACD_LEVERS'; -import { findOption, createCombinationValue } from '../../utils'; +import { + findOption, + hasCombinationLeverChanged, + radioValueOptionSelected, + findValueOption, + hasLeverValueChanged +} from '../../utils'; const sortLevers = (leverA, leverB) => leverA.lever_group_order - leverB.lever_group_order; @@ -36,21 +42,58 @@ const leverErrorCount = (state) => { return state.caseDistributionLevers.leversErrors.length; }; +const getLeversAsArray = (state) => { + return Object.values(getLevers(state)).flat(); +}; + +const getSimpleLevers = (state) => { + return getLeversAsArray(state).filter((lever) => + lever.data_type !== ACD_LEVERS.data_types.radio && + lever.data_type !== ACD_LEVERS.data_types.combination + ); +}; + +const getCombinationLevers = (state) => { + return getLeversAsArray(state).filter((lever) => + lever.data_type === ACD_LEVERS.data_types.combination + ); +}; + +const getRadioLevers = (state) => { + return getLeversAsArray(state).filter((lever) => + lever.data_type === ACD_LEVERS.data_types.radio + ); +}; + /** - * WILL NEED UPDATING WHEN RADIO AND COMBINATION LEVERS ARE EDITABLE + * Determine which levers have changed + * + * For radio levers compare if value has changed + * + * For combination levers compare if either is_toggle_active or value has changed + * + * For simple lever data types compare if value has changed */ export const changedLevers = createSelector( - [getLevers], - (levers) => { - return Object.values(levers).flat(). - filter((lever) => - lever.data_type !== ACD_LEVERS.data_types.radio && - lever.data_type !== ACD_LEVERS.data_types.combination && - lever.backendValue !== null && - `${lever.value}` !== lever.backendValue - ). + [getSimpleLevers, getCombinationLevers, getRadioLevers], + (simpleLevers, combinationLevers, radioLevers) => { + const changedSimpleLevers = simpleLevers.filter((lever) => + hasLeverValueChanged(lever) + ); + + const changedCombinationLevers = combinationLevers.filter((lever) => + hasCombinationLeverChanged(lever) + ); + + // Keeping separated in case there is a need to add additional checks + const changedRadioLevers = radioLevers.filter((lever) => + hasLeverValueChanged(lever) + ); + + return changedSimpleLevers.concat(changedCombinationLevers, changedRadioLevers). sort((leverA, leverB) => sortLevers(leverA, leverB)); } + ); export const hasChangedLevers = (state) => changedLevers(state).length > 0; @@ -82,9 +125,9 @@ const updateLeverGroup = (state, leverGroup, leverItem, updateLeverValue) => ); /** - * Updates levers of data type number, boolean, and text + * Used when updating the value of a lever */ -export const createUpdatedLever = (state, action) => { +export const updateLeverGroupForValue = (state, action) => { const { leverGroup, leverItem, value } = action.payload; const updateLeverValue = (lever) => { @@ -95,18 +138,13 @@ export const createUpdatedLever = (state, action) => { }; /** - * Do not trust this code. It is untested - * WILL NEED UPDATING WHEN RADIO AND COMBINATION LEVERS ARE EDITABLE + * Used when updating the is_toggle_active of a lever */ -export const createUpdatedRadioLever = (state, action) => { - const { leverGroup, leverItem, value, optionValue } = action.payload; +export const updateLeverGroupForIsToggleActive = (state, action) => { + const { leverGroup, leverItem, toggleValue } = action.payload; const updateLeverValue = (lever) => { - const selectedOption = findOption(lever, value); - - selectedOption.value = optionValue; - - return { ...lever, currentValue: optionValue }; + return { ...lever, is_toggle_active: toggleValue }; }; return updateLeverGroup(state, leverGroup, leverItem, updateLeverValue); @@ -127,16 +165,40 @@ export const hasNoLeverErrors = createSelector( ); /** - * Do not trust this code. It is untested - * WILL NEED UPDATING WHEN RADIO AND COMBINATION LEVERS ARE EDITABLE + * Used when updating the a radio lever + * Pass in the selected option and a value if the selected option is value + * + * This will break if a Radio lever has more than one option that has an input + * + * If value is the selected Radio option + * Update lever.value to optionValue passed in + * Set valueOptionValue to value passed in + * + * If omit or infinite is the selected Radio option + * Update lever.value to the value passed in + * Set valueOptionValue to value in value's option */ -export const createUpdatedCombinationLever = (state, action) => { - const { leverGroup, leverItem, value, toggleValue } = action.payload; +export const updateLeverGroupForRadioLever = (state, action) => { + const { leverGroup, leverItem, value, optionValue } = action.payload; const updateLeverValue = (lever) => { - const newValue = createCombinationValue(toggleValue, value); + const selectedOption = findOption(lever, value); + const isValueOption = radioValueOptionSelected(value); + const valueOptionValue = isValueOption ? optionValue : findValueOption(lever).value; + const leverValue = isValueOption ? optionValue : value; - return { ...lever, currentValue: newValue, is_toggle_active: toggleValue }; + // Set all options to not selected + lever.options.forEach((option) => option.selected = false); + + selectedOption.value = optionValue; + selectedOption.selected = true; + + return { + ...lever, + value: leverValue, + selectedOption: value, + valueOptionValue + }; }; return updateLeverGroup(state, leverGroup, leverItem, updateLeverValue); diff --git a/client/app/caseDistribution/utils.js b/client/app/caseDistribution/utils.js index 1a03ef7e340..3ec56804c73 100644 --- a/client/app/caseDistribution/utils.js +++ b/client/app/caseDistribution/utils.js @@ -1,8 +1,9 @@ import ACD_LEVERS from '../../constants/ACD_LEVERS'; export const findOption = (lever, value) => lever.options.find((option) => option.item === value); - -export const createCombinationValue = (toggleValue, value) => `${toggleValue}-${value}`; +export const findSelectedOption = (lever) => lever.options.find((option) => option.selected); +export const findValueOption = (lever) => findOption(lever, ACD_LEVERS.value); +export const radioValueOptionSelected = (item) => ACD_LEVERS.value === item; /** * Add backendValue attributes to each lever @@ -18,23 +19,31 @@ export const createUpdatedLeversWithValues = (levers) => { return leverGroups.reduce((updatedLevers, leverGroup) => { updatedLevers[leverGroup] = levers[leverGroup]?.map((lever) => { + // All levers should have a backendValue added as this is used for the following + // - showing the user what value was in the SaveModal + // - checking if the lever has changed and Save button should be enabled let additionalValues = { backendValue: lever.value }; const dataType = lever.data_type; - // Only add a new property for radio and combination data types as these have special handling logic + // Add new properties for radio and combination data types as these have special handling logic // to retrieve value if (dataType === ACD_LEVERS.data_types.radio) { + const selectedOption = findSelectedOption(lever).item; + const valueOptionValue = radioValueOptionSelected(selectedOption) ? + lever.value : findValueOption(lever).value; + additionalValues = { - currentValue: findOption(lever, lever.value).value, - backendValue: findOption(lever, lever.value).value, + ...additionalValues, + selectedOption, + valueOptionValue, }; } else if (dataType === ACD_LEVERS.data_types.combination) { additionalValues = { - currentValue: createCombinationValue(lever.is_toggle_active, lever.value), - backendValue: createCombinationValue(lever.is_toggle_active, lever.value) + ...additionalValues, + backendIsToggleActive: lever.is_toggle_active }; } @@ -121,7 +130,9 @@ export const validateLeverInput = (lever, value) => { errors.push({ leverItem: lever.item, message: ACD_LEVERS.validation_error_message.minimum_not_met }); } if (maxValue && value > maxValue) { - errors.push({ leverItem: lever.item, message: ACD_LEVERS.validation_error_message.out_of_bounds }); + const message = ACD_LEVERS.validation_error_message.out_of_bounds.replace('%s', maxValue); + + errors.push({ leverItem: lever.item, message }); } } @@ -140,3 +151,21 @@ export const dynamicallyAddAsterisk = (lever) => { return (lever.algorithms_used.includes(ACD_LEVERS.algorithms.proportion) && lever.algorithms_used.includes(ACD_LEVERS.algorithms.docket) ? '*' : ''); }; + +/** + * if is_toggle_active was false then set to true and value was updated + * return true + * if is_toggle_active was true, value was updated then is_toggle_active was set to false + * return true + * if is_toggle_active didn't change, value was udpated + * return true + * if neither value or is_toggle_active changed + * return false + */ +export const hasCombinationLeverChanged = (lever) => + (lever.backendIsToggleActive !== lever.is_toggle_active) || + (lever.backendValue !== null && + `${lever.value}` !== lever.backendValue); + +export const hasLeverValueChanged = (lever) => lever.backendValue !== null && `${lever.value}` !== lever.backendValue; + diff --git a/client/app/queue/components/AssignToAttorneyWidget.jsx b/client/app/queue/components/AssignToAttorneyWidget.jsx index cc8af42560c..48aaa0a6317 100644 --- a/client/app/queue/components/AssignToAttorneyWidget.jsx +++ b/client/app/queue/components/AssignToAttorneyWidget.jsx @@ -165,14 +165,19 @@ export class AssignToAttorneyWidget extends React.PureComponent { let errorDetail; - try { - errorDetail = error.response.body.errors[0].detail; - } catch (ex) { - errorDetail = this.props.isModal && userId ? + errorDetail = error?.response?.body?.errors[0]?.detail; + + // eslint-disable-next-line no-undefined + if (errorDetail === null || errorDetail === undefined) { + if (this.props.isModal && userId) { + errorDetail = {COPY.ASSIGN_WIDGET_ASSIGNMENT_ERROR_DETAIL_MODAL_LINK} {COPY.ASSIGN_WIDGET_ASSIGNMENT_ERROR_DETAIL_MODAL} - : COPY.ASSIGN_WIDGET_ASSIGNMENT_ERROR_DETAIL; + ; + } else { + errorDetail = COPY.ASSIGN_WIDGET_ASSIGNMENT_ERROR_DETAIL; + } } return this.props.showErrorMessage({ diff --git a/client/app/util/ApiUtil.js b/client/app/util/ApiUtil.js index 99e4325d968..3e4a03540ce 100644 --- a/client/app/util/ApiUtil.js +++ b/client/app/util/ApiUtil.js @@ -8,10 +8,13 @@ import { timeFunctionPromise } from '../util/PerfDebug'; import moment from 'moment'; export const STANDARD_API_TIMEOUT_MILLISECONDS = 60 * 1000; +export const DEMO_API_TIMEOUT_MILLISECONDS = 2 * STANDARD_API_TIMEOUT_MILLISECONDS; export const RESPONSE_COMPLETE_LIMIT_MILLISECONDS = 5 * 60 * 1000; +// eslint-disable-next-line no-process-env +const onDemo = process.env.DEPLOY_ENV === 'demo'; const defaultTimeoutSettings = { - response: STANDARD_API_TIMEOUT_MILLISECONDS, + response: onDemo ? DEMO_API_TIMEOUT_MILLISECONDS : STANDARD_API_TIMEOUT_MILLISECONDS, deadline: RESPONSE_COMPLETE_LIMIT_MILLISECONDS }; diff --git a/client/constants/ACD_LEVERS.json b/client/constants/ACD_LEVERS.json index 99351b9b248..ca73b8769bc 100644 --- a/client/constants/ACD_LEVERS.json +++ b/client/constants/ACD_LEVERS.json @@ -27,6 +27,6 @@ }, "validation_error_message": { "minimum_not_met": "Please enter a value greater than or equal to 0", - "out_of_bounds": "Please enter a value from 0 to 999" + "out_of_bounds": "Please enter a value from 0 to %s" } } diff --git a/client/webpack.config.js b/client/webpack.config.js index d0cd3058cf5..22bc0fc7f78 100644 --- a/client/webpack.config.js +++ b/client/webpack.config.js @@ -1,6 +1,7 @@ const webpack = require('webpack'); const path = require('path'); const ReactRefreshWebpackPlugin = require('@pmmmwh/react-refresh-webpack-plugin'); +const Dotenv = require('dotenv-webpack'); const devBuild = process.env.NODE_ENV !== 'production'; // eslint-disable-line no-process-env const testBuild = process.env.NODE_ENV === 'test'; // eslint-disable-line no-process-env @@ -18,6 +19,10 @@ const config = { new webpack.EnvironmentPlugin({ NODE_ENV: 'development' }), devBuild && new webpack.HotModuleReplacementPlugin(), devBuild && !testBuild && new ReactRefreshWebpackPlugin(), + // See https://github.com/mrsteele/dotenv-webpack/blob/master/README.md#properties + new Dotenv({ + systemvars: true, + }) ].filter(Boolean), devServer: { headers: { diff --git a/db/migrate/20240130152058_update_case_distribution_levers_is_toggle_active.rb b/db/migrate/20240130152058_update_case_distribution_levers_is_toggle_active.rb new file mode 100644 index 00000000000..6bbae25e924 --- /dev/null +++ b/db/migrate/20240130152058_update_case_distribution_levers_is_toggle_active.rb @@ -0,0 +1,7 @@ +class UpdateCaseDistributionLeversIsToggleActive < ActiveRecord::Migration[5.2] + def change + safety_assured do + change_column_null :case_distribution_levers, :is_toggle_active, true + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 98e0ba6664e..e1b0f69631f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2024_01_29_200527) do +ActiveRecord::Schema.define(version: 2024_01_30_152058) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -362,7 +362,7 @@ t.string "data_type", null: false, comment: "Indicates which type of record either BOOLEAN/RADIO/COMBO" t.text "description", comment: "Indicates the description of the Lever" t.boolean "is_disabled_in_ui", null: false, comment: "Determines behavior in the controls page" - t.boolean "is_toggle_active", null: false, comment: "used for the docket time goals, otherwise it is true and unused" + t.boolean "is_toggle_active", comment: "used for the docket time goals, otherwise it is true and unused" t.string "item", null: false, comment: "Is unique value to identify the Case Distribution lever" t.string "lever_group", default: "", null: false, comment: "Case Distribution lever grouping" t.integer "lever_group_order", null: false, comment: "determines the order that the lever appears in each section of inputs, and the order in the history table" diff --git a/db/seeds/case_distribution_levers.rb b/db/seeds/case_distribution_levers.rb index 62f52ba630c..0d72a4ff99c 100644 --- a/db/seeds/case_distribution_levers.rb +++ b/db/seeds/case_distribution_levers.rb @@ -1,603 +1,41 @@ +# frozen_string_literal: true + +# Invoke with: +# RequestStore[:current_user] = User.system_user +# Dir[Rails.root.join("db/seeds/*.rb")].sort.each { |f| require f } +# Seeds::CaseDistributionLevers module Seeds class CaseDistributionLevers < Base + # Creates new levers and updates existing levers + # For existing levers it does not update every field, there is a separate but DANGEROUS operation for that def seed! + updated_levers = [] + CaseDistributionLevers.levers.each do |lever| - next if CaseDistributionLever.find_by_item(lever[:item]) - create_lever lever + existing_lever = CaseDistributionLever.find_by_item(lever[:item]) + if existing_lever.present? + updated_levers << update_lever(lever, existing_lever) + else + create_lever(lever) + end end - end - def self.levers - [ - { - item: Constants.DISTRIBUTION.maximum_direct_review_proportion, - title: Constants.DISTRIBUTION.maximum_direct_review_proportion_title, - description: "Sets the maximum number of direct reviews in relation to due direct review proportion to prevent a complete halt to work on other dockets should demand for direct reviews approach the Board's capacity.", - data_type: Constants.ACD_LEVERS.data_types.number, - value: 0.07, - unit: '%', - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion], - lever_group: Constants.ACD_LEVERS.lever_groups.static, - lever_group_order: 1000 - }, - { - item: Constants.DISTRIBUTION.minimum_legacy_proportion, - title: Constants.DISTRIBUTION.minimum_legacy_proportion_title, - description: 'Sets the minimum proportion of legacy appeals that will be distributed.', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 0.9, - unit: '%', - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion], - lever_group: Constants.ACD_LEVERS.lever_groups.static, - lever_group_order: 1001 - }, - { - item: Constants.DISTRIBUTION.nod_adjustment, - title: Constants.DISTRIBUTION.nod_adjustment_title, - description: 'Applied for docket balancing reflecting the likelihood that NODs will advance to a Form 9.', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 0.4, - unit: '%', - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion], - lever_group: Constants.ACD_LEVERS.lever_groups.static, - lever_group_order: 1002 - }, - { - item: Constants.DISTRIBUTION.bust_backlog, - title: Constants.DISTRIBUTION.bust_backlog_title, - description: 'Distribute legacy cases tied to a judge to the Board-provided limit of 30, regardless of the legacy docket range.', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: true, - unit: '', - is_toggle_active: false, - is_disabled_in_ui: true, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion], - lever_group: Constants.ACD_LEVERS.lever_groups.static, - lever_group_order: 1003 - }, - { - item: Constants.DISTRIBUTION.alternative_batch_size, - title: Constants.DISTRIBUTION.alternative_batch_size_title, - description: 'Sets case-distribution batch size for judges who do not have their own attorney teams.', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 15, - unit: Constants.ACD_LEVERS.cases, - is_toggle_active: true, - is_disabled_in_ui: false, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket, Constants.ACD_LEVERS.algorithms.proportion], - lever_group: Constants.ACD_LEVERS.lever_groups.batch, - lever_group_order: 2000 - }, - { - item: Constants.DISTRIBUTION.batch_size_per_attorney, - title: Constants.DISTRIBUTION.batch_size_per_attorney_title, - description: 'Sets case-distribution batch size for judges with attorney teams. The value for this data element is per attorney.', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 3, - unit: Constants.ACD_LEVERS.cases, - is_toggle_active: true, - is_disabled_in_ui: false, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket, Constants.ACD_LEVERS.algorithms.proportion], - lever_group: Constants.ACD_LEVERS.lever_groups.batch, - lever_group_order: 2001 - }, - { - item: Constants.DISTRIBUTION.request_more_cases_minimum, - title: Constants.DISTRIBUTION.request_more_cases_minimum_title, - description: 'Sets the number of remaining cases a VLJ must have equal to or less than to request more cases. (The number entered is used as equal to or less than)', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 8, - unit: Constants.ACD_LEVERS.cases, - is_toggle_active: true, - is_disabled_in_ui: false, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket, Constants.ACD_LEVERS.algorithms.proportion], - lever_group: Constants.ACD_LEVERS.lever_groups.batch, - lever_group_order: 2002 - }, - { - item: Constants.DISTRIBUTION.ama_hearing_case_affinity_days, - title: Constants.DISTRIBUTION.ama_hearing_case_affinity_days_title, - description: 'For non-priority AMA Hearing cases, sets the number of days an AMA Hearing Case is tied to the judge that held the hearing.', - data_type: Constants.ACD_LEVERS.data_types.radio, - value: 'value', - unit: Constants.ACD_LEVERS.days, - options: [ - { - item: 'value', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 60, - text: 'Attempt distribution to current judge for max of:', - unit: Constants.ACD_LEVERS.days, - min_value: 0, - max_value: 100, - }, - { - item: Constants.ACD_LEVERS.infinite, - value: Constants.ACD_LEVERS.infinite, - text: 'Always distribute to current judge', - }, - { - item: Constants.ACD_LEVERS.omit, - value: Constants.ACD_LEVERS.omit, - text: 'Omit variable from distribution rules', - } - ], - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: 100, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.affinity, - lever_group_order: 3000 - }, - { - item: Constants.DISTRIBUTION.ama_hearing_case_aod_affinity_days, - title: Constants.DISTRIBUTION.ama_hearing_case_aod_affinity_days_title, - description: 'Sets the number of days an AMA Hearing appeal that is also AOD will respect the affinity to the most-recent hearing judge before distributing the appeal to any available judge.', - data_type: Constants.ACD_LEVERS.data_types.radio, - value: 'value', - unit: Constants.ACD_LEVERS.days, - options: [ - { - item: 'value', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 14, - text: 'Attempt distribution to current judge for max of:', - unit: Constants.ACD_LEVERS.days - }, - { - item: Constants.ACD_LEVERS.infinite, - data_type: '', - value: Constants.ACD_LEVERS.infinite, - text: 'Always distribute to current judge', - unit: '' - }, - { - item: Constants.ACD_LEVERS.omit, - data_type: '', - value: Constants.ACD_LEVERS.omit, - text: 'Omit variable from distribution rules', - unit: '' - } - ], - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: 100, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.affinity, - lever_group_order: 3001 - }, - { - item: Constants.DISTRIBUTION.cavc_affinity_days, - title: Constants.DISTRIBUTION.cavc_affinity_days_title, - description: 'Sets the number of days a case returned from CAVC respects the affinity to the judge who authored a decision before distributing the appeal to any available judge. This does not include Legacy CAVC Remand Appeals with a hearing held.', - data_type: Constants.ACD_LEVERS.data_types.radio, - value: 'value', - unit: Constants.ACD_LEVERS.days, - options: [ - { - item: 'value', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 21, - text: 'Attempt distribution to current judge for max of:', - unit: Constants.ACD_LEVERS.days - }, - { - item: Constants.ACD_LEVERS.infinite, - value: Constants.ACD_LEVERS.infinite, - text: 'Always distribute to current judge' - }, - { - item: Constants.ACD_LEVERS.omit, - value: Constants.ACD_LEVERS.omit, - text: 'Omit variable from distribution rules' - } - ], - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: 100, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket, Constants.ACD_LEVERS.algorithms.proportion], - lever_group: Constants.ACD_LEVERS.lever_groups.affinity, - lever_group_order: 3002 - }, - { - item: Constants.DISTRIBUTION.cavc_aod_affinity_days, - title: Constants.DISTRIBUTION.cavc_aod_affinity_days_title, - description: 'Sets the number of days appeals returned from CAVC that are also AOD respect the affinity to the deciding judge. This is not applicable for legacy apeals for which the deciding judge conducted the most recent hearing.', - data_type: Constants.ACD_LEVERS.data_types.radio, - value: 'value', - unit: Constants.ACD_LEVERS.days, - options: [ - { - item: 'value', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 14, - text: 'Attempt distribution to current judge for max of:', - unit: Constants.ACD_LEVERS.days - }, - { - item: Constants.ACD_LEVERS.infinite, - value: Constants.ACD_LEVERS.infinite, - text: 'Always distribute to current judge', - }, - { - item: Constants.ACD_LEVERS.omit, - value: Constants.ACD_LEVERS.omit, - text: 'Omit variable from distribution rules', - } - ], - is_toggle_active: false, - is_disabled_in_ui: true, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion], - lever_group: Constants.ACD_LEVERS.lever_groups.affinity, - lever_group_order: 3003 - }, - { - item: Constants.DISTRIBUTION.aoj_affinity_days, - title: Constants.DISTRIBUTION.aoj_affinity_days_title, - description: 'Sets the number of days an appeal respects the affinity to the deciding judge for Legacy AOJ Remand Returned appeals with no hearing held before distributing the appeal to any available judge.', - data_type: Constants.ACD_LEVERS.data_types.radio, - value: 'value', - unit: Constants.ACD_LEVERS.days, - options: [ - { - item: 'value', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 60, - text: 'Attempt distribution to current judge for max of:', - unit: Constants.ACD_LEVERS.days - }, - { - item: Constants.ACD_LEVERS.infinite, - data_type: '', - value: Constants.ACD_LEVERS.infinite, - text: 'Always distribute to current judge', - unit: '' - }, - { - item: Constants.ACD_LEVERS.omit, - data_type: '', - value: Constants.ACD_LEVERS.omit, - text: 'Omit variable from distribution rules', - unit: '' - } - ], - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: 100, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.affinity, - lever_group_order: 3004 - }, - { - item: Constants.DISTRIBUTION.aoj_aod_affinity_days, - title: Constants.DISTRIBUTION.aoj_aod_affinity_days_title, - description: 'Sets the number of days legacy remand Returned appeals that are also AOD (and may or may not have been CAVC at one time) respect the affinity before distributing the appeal to any available jduge. Affects appeals with hearing held when the remanding judge is not the hearing judge, or any legacy AOD + AOD appeal with no hearing held (whether or not it had been CAVC at one time).', - data_type: Constants.ACD_LEVERS.data_types.radio, - value: 'value', - unit: Constants.ACD_LEVERS.days, - options: [ - { - item: 'value', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 14, - text: 'Attempt distribution to current judge for max of:', - unit: Constants.ACD_LEVERS.days - }, - { - item: Constants.ACD_LEVERS.infinite, - data_type: '', - value: Constants.ACD_LEVERS.infinite, - text: 'Always distribute to current judge', - unit: '' - }, - { - item: Constants.ACD_LEVERS.omit, - data_type: '', - value: Constants.ACD_LEVERS.omit, - text: 'Omit variable from distribution rules', - unit: '' - } - ], - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: 100, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.affinity, - lever_group_order: 3005 - }, - { - item: Constants.DISTRIBUTION.aoj_cavc_affinity_days, - title: Constants.DISTRIBUTION.aoj_cavc_affinity_days_title, - description: 'Sets the number of days AOJ appeals that were CAVC at some time respect the affinity before the appeal is distributed to any available judge. This applies to any AOJ + CAVC appeal with no hearing held, or those with a hearing held when the remanding judge is not the hearing judge.', - data_type: Constants.ACD_LEVERS.data_types.radio, - value: 'value', - unit: Constants.ACD_LEVERS.days, - options: [ - { - item: 'value', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 21, - text: 'Attempt distribution to current judge for max of:', - unit: Constants.ACD_LEVERS.days - }, - { - item: Constants.ACD_LEVERS.infinite, - data_type: '', - value: Constants.ACD_LEVERS.infinite, - text: 'Always distribute to current judge', - unit: '' - }, - { - item: Constants.ACD_LEVERS.omit, - data_type: '', - value: Constants.ACD_LEVERS.omit, - text: 'Omit variable from distribution rules', - unit: '' - } - ], - is_toggle_active: true, - is_disabled_in_ui: true, - min_value: 0, - max_value: 100, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.affinity, - lever_group_order: 3006 - }, - { - item: Constants.DISTRIBUTION.ama_hearings_start_distribution_prior_to_goals, - title: 'AMA Hearings Start Distribution Prior to Goals', - description: '', - data_type: Constants.ACD_LEVERS.data_types.combination, - value: 60, - unit: Constants.ACD_LEVERS.days, - options: [ - { - item: 'value', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: true, - text: 'This feature is turned on or off', - unit: '' - } - ], - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_distribution_prior, - lever_group_order: 4000 - }, - { - item: Constants.DISTRIBUTION.ama_direct_review_start_distribution_prior_to_goals, - title: 'AMA Direct Review Start Distribution Prior to Goals', - description: '', - data_type: Constants.ACD_LEVERS.data_types.combination, - value: 365, - unit: Constants.ACD_LEVERS.days, - options: [ - { - item: 'value', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: true, - text: 'This feature is turned on or off', - unit: '' - } - ], - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_distribution_prior, - lever_group_order: 4001 - }, - { - item: Constants.DISTRIBUTION.ama_evidence_submission_start_distribution_prior_to_goals, - title: 'AMA Evidence Submission Start Distribution Prior to Goals', - description: '', - data_type: Constants.ACD_LEVERS.data_types.combination, - value: 60, - unit: Constants.ACD_LEVERS.days, - options: [ - { - item: 'value', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: true, - text: 'This feature is turned on or off', - unit: '' - } - ], - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_distribution_prior, - lever_group_order: 4002 - }, - { - item: Constants.DISTRIBUTION.ama_hearings_docket_time_goals, - title: 'AMA Hearings Docket Time Goals', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 730, - unit: Constants.ACD_LEVERS.days, - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_time_goal, - lever_group_order: 4003 - }, - { - item: Constants.DISTRIBUTION.ama_direct_review_docket_time_goals, - title: 'AMA Direct Review Docket Time Goals', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 365, - unit: Constants.ACD_LEVERS.days, - is_toggle_active: true, - is_disabled_in_ui: false, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_time_goal, - lever_group_order: 4004 - }, - { - item: Constants.DISTRIBUTION.ama_evidence_submission_docket_time_goals, - title: 'AMA Evidence Submission Docket Time Goals', - data_type: Constants.ACD_LEVERS.data_types.number, - value: 550, - unit: Constants.ACD_LEVERS.days, - is_toggle_active: false, - is_disabled_in_ui: true, - min_value: 0, - max_value: nil, - algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_time_goal, - lever_group_order: 4005 - }, - { - item: Constants.DISTRIBUTION.disable_legacy_priority, - title: Constants.DISTRIBUTION.disable_legacy_priority_title, - description: '', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: false, - unit: '', - is_toggle_active: false, - is_disabled_in_ui: true, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, - lever_group_order: 10 - }, - { - item: Constants.DISTRIBUTION.disable_legacy_non_priority, - title: Constants.DISTRIBUTION.disable_legacy_non_priority_title, - description: '', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: false, - unit: '', - is_toggle_active: false, - is_disabled_in_ui: true, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, - lever_group_order: 101 - }, - { - item: Constants.DISTRIBUTION.disable_ama_non_priority_hearing, - title: Constants.DISTRIBUTION.disable_ama_non_priority_hearing_title, - description: '', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: false, - unit: '', - is_toggle_active: false, - is_disabled_in_ui: true, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, - lever_group_order: 102 - }, - { - item: Constants.DISTRIBUTION.disable_ama_non_priority_direct_review, - title: Constants.DISTRIBUTION.disable_ama_non_priority_direct_review_title, - description: '', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: false, - unit: '', - is_toggle_active: false, - is_disabled_in_ui: true, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, - lever_group_order: 103 - }, - { - item: Constants.DISTRIBUTION.disable_ama_non_priority_evidence_sub, - title: Constants.DISTRIBUTION.disable_ama_non_priority_evidence_sub_title, - description: '', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: false, - unit: '', - is_toggle_active: false, - is_disabled_in_ui: true, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, - lever_group_order: 104 - }, - { - item: Constants.DISTRIBUTION.disable_ama_priority_hearing, - title: Constants.DISTRIBUTION.disable_ama_priority_hearing_title, - description: '', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: false, - unit: '', - is_toggle_active: false, - is_disabled_in_ui: true, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, - lever_group_order: 105 - }, - { - item: Constants.DISTRIBUTION.disable_ama_priority_direct_review, - title: Constants.DISTRIBUTION.disable_ama_priority_direct_review_title, - description: '', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: false, - unit: '', - is_toggle_active: false, - is_disabled_in_ui: true, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, - lever_group_order: 106 - }, - { - item: Constants.DISTRIBUTION.disable_ama_priority_evidence_sub, - title: Constants.DISTRIBUTION.disable_ama_priority_evidence_sub_title, - description: '', - data_type: Constants.ACD_LEVERS.data_types.boolean, - value: false, - unit: '', - is_toggle_active: false, - is_disabled_in_ui: true, - algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], - lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, - lever_group_order: 107 - }, - ] + validate_levers_creation + updated_levers.compact! + puts "#{updated_levers.count} levers updated: #{updated_levers}" if updated_levers.count > 0 end private - def create_lever lever - CaseDistributionLever.create( + def create_lever(lever) + lever = CaseDistributionLever.create( item: lever[:item], title: lever[:title], description: lever[:description], data_type: lever[:data_type], value: lever[:value].to_s, unit: lever[:unit], - is_toggle_active: lever[:is_toggle_active] || false, + is_toggle_active: lever[:is_toggle_active], is_disabled_in_ui: lever[:is_disabled_in_ui] || false, min_value: lever[:min_value], max_value: lever[:max_value], @@ -607,6 +45,688 @@ def create_lever lever lever_group: lever[:lever_group], lever_group_order: lever[:lever_group_order] ) + + puts "*********************************************" + puts lever.errors.full_messages unless lever.valid? + puts "*********************************************" + end + + # For properties missing those were intentionally ignored so that they would not + # be easy to change using this seed data script. + # + # The reason being is the properties will either be modified by users, changing them would break the application, + # or is a JSON object with a complex structure that should be carefully changed + # + # TODO remove is_toggle_active after first run of this in all envs + def update_lever(lever, existing_lever) + return unless lever_updated?(lever, existing_lever) + + existing_lever.update( + title: lever[:title], + description: lever[:description], + is_toggle_active: lever[:is_toggle_active], + is_disabled_in_ui: lever[:is_disabled_in_ui], + unit: lever[:unit], + min_value: lever[:min_value], + max_value: lever[:max_value], + algorithms_used: lever[:algorithms_used], + control_group: lever[:control_group], + lever_group_order: lever[:lever_group_order] + ) + + existing_lever.item if existing_lever.valid? + end + + # For properties missing those were intentionally ignored so that they would not + # be easy to change using this seed data script. + # + # The reason being is the properties will either be modified by users, changing them would break the application, + # or is a JSON object with a complex structure that should be carefully changed + # + # TODO remove is_toggle_active after first run of this in all envs + def lever_updated?(lever, existing_lever) + existing_lever.title != lever[:title] || + existing_lever.description != lever[:description] || + existing_lever.is_toggle_active != lever[:is_toggle_active] || + existing_lever.is_disabled_in_ui != lever[:is_disabled_in_ui] || + existing_lever.unit != lever[:unit] || + existing_lever.min_value != lever[:min_value] || + existing_lever.max_value != lever[:max_value] || + existing_lever.algorithms_used != lever[:algorithms_used] || + existing_lever.control_group != lever[:control_group] || + existing_lever.lever_group_order != lever[:lever_group_order] + end + + def validate_levers_creation + levers = CaseDistributionLevers.levers.map { |lever| lever[:item] } + existing_levers = CaseDistributionLever.all.map(&:item) + + puts "#{CaseDistributionLever.count} levers exist" + puts "Levers not created #{levers - existing_levers}" if levers.length != existing_levers.length + end + + class << self + def levers + [ + { + item: Constants.DISTRIBUTION.maximum_direct_review_proportion, + title: Constants.DISTRIBUTION.maximum_direct_review_proportion_title, + description: "Sets the maximum number of direct reviews in relation to due direct review proportion to prevent a complete halt to work on other dockets should demand for direct reviews approach the Board's capacity.", + data_type: Constants.ACD_LEVERS.data_types.number, + value: 0.07, + unit: "%", + is_disabled_in_ui: true, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion], + lever_group: Constants.ACD_LEVERS.lever_groups.static, + lever_group_order: 1000 + }, + { + item: Constants.DISTRIBUTION.minimum_legacy_proportion, + title: Constants.DISTRIBUTION.minimum_legacy_proportion_title, + description: "Sets the minimum proportion of legacy appeals that will be distributed.", + data_type: Constants.ACD_LEVERS.data_types.number, + value: 0.9, + unit: "%", + is_disabled_in_ui: true, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion], + lever_group: Constants.ACD_LEVERS.lever_groups.static, + lever_group_order: 1001 + }, + { + item: Constants.DISTRIBUTION.nod_adjustment, + title: Constants.DISTRIBUTION.nod_adjustment_title, + description: "Applied for docket balancing reflecting the likelihood that NODs will advance to a Form 9.", + data_type: Constants.ACD_LEVERS.data_types.number, + value: 0.4, + unit: "%", + is_disabled_in_ui: true, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion], + lever_group: Constants.ACD_LEVERS.lever_groups.static, + lever_group_order: 1002 + }, + { + item: Constants.DISTRIBUTION.bust_backlog, + title: Constants.DISTRIBUTION.bust_backlog_title, + description: "Distribute legacy cases tied to a judge to the Board-provided limit of 30, regardless of the legacy docket range.", + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: true, + unit: "", + is_disabled_in_ui: true, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion], + lever_group: Constants.ACD_LEVERS.lever_groups.static, + lever_group_order: 1003 + }, + { + item: Constants.DISTRIBUTION.alternative_batch_size, + title: Constants.DISTRIBUTION.alternative_batch_size_title, + description: "Sets case-distribution batch size for judges who do not have their own attorney teams.", + data_type: Constants.ACD_LEVERS.data_types.number, + value: 15, + unit: Constants.ACD_LEVERS.cases, + is_disabled_in_ui: false, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket, Constants.ACD_LEVERS.algorithms.proportion], + lever_group: Constants.ACD_LEVERS.lever_groups.batch, + lever_group_order: 2000 + }, + { + item: Constants.DISTRIBUTION.batch_size_per_attorney, + title: Constants.DISTRIBUTION.batch_size_per_attorney_title, + description: "Sets case-distribution batch size for judges with attorney teams. The value for this data element is per attorney.", + data_type: Constants.ACD_LEVERS.data_types.number, + value: 3, + unit: Constants.ACD_LEVERS.cases, + is_disabled_in_ui: false, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket, Constants.ACD_LEVERS.algorithms.proportion], + lever_group: Constants.ACD_LEVERS.lever_groups.batch, + lever_group_order: 2001 + }, + { + item: Constants.DISTRIBUTION.request_more_cases_minimum, + title: Constants.DISTRIBUTION.request_more_cases_minimum_title, + description: "Sets the number of remaining cases a VLJ must have equal to or less than to request more cases. (The number entered is used as equal to or less than)", + data_type: Constants.ACD_LEVERS.data_types.number, + value: 8, + unit: Constants.ACD_LEVERS.cases, + is_disabled_in_ui: false, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket, Constants.ACD_LEVERS.algorithms.proportion], + lever_group: Constants.ACD_LEVERS.lever_groups.batch, + lever_group_order: 2002 + }, + { + item: Constants.DISTRIBUTION.ama_hearing_case_affinity_days, + title: Constants.DISTRIBUTION.ama_hearing_case_affinity_days_title, + description: "For non-priority AMA Hearing cases, sets the number of days an AMA Hearing Case is tied to the judge that held the hearing.", + data_type: Constants.ACD_LEVERS.data_types.radio, + value: "60", + unit: Constants.ACD_LEVERS.days, + options: [ + { + item: Constants.ACD_LEVERS.value, + data_type: Constants.ACD_LEVERS.data_types.number, + value: 60, + text: "Attempt distribution to current judge for max of:", + unit: Constants.ACD_LEVERS.days, + min_value: 0, + max_value: 999, + selected: true + }, + { + item: Constants.ACD_LEVERS.infinite, + value: Constants.ACD_LEVERS.infinite, + text: "Always distribute to current judge" + }, + { + item: Constants.ACD_LEVERS.omit, + value: Constants.ACD_LEVERS.omit, + text: "Omit variable from distribution rules" + } + ], + is_disabled_in_ui: false, + min_value: 0, + max_value: 999, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.affinity, + lever_group_order: 3000 + }, + { + item: Constants.DISTRIBUTION.ama_hearing_case_aod_affinity_days, + title: Constants.DISTRIBUTION.ama_hearing_case_aod_affinity_days_title, + description: "Sets the number of days an AMA Hearing appeal that is also AOD will respect the affinity to the most-recent hearing judge before distributing the appeal to any available judge.", + data_type: Constants.ACD_LEVERS.data_types.radio, + value: "14", + unit: Constants.ACD_LEVERS.days, + options: [ + { + item: Constants.ACD_LEVERS.value, + data_type: Constants.ACD_LEVERS.data_types.number, + value: 14, + text: "Attempt distribution to current judge for max of:", + unit: Constants.ACD_LEVERS.days, + selected: true + }, + { + item: Constants.ACD_LEVERS.infinite, + data_type: "", + value: Constants.ACD_LEVERS.infinite, + text: "Always distribute to current judge", + unit: "" + }, + { + item: Constants.ACD_LEVERS.omit, + data_type: "", + value: Constants.ACD_LEVERS.omit, + text: "Omit variable from distribution rules", + unit: "" + } + ], + is_disabled_in_ui: false, + min_value: 0, + max_value: 999, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.affinity, + lever_group_order: 3001 + }, + { + item: Constants.DISTRIBUTION.cavc_affinity_days, + title: Constants.DISTRIBUTION.cavc_affinity_days_title, + description: "Sets the number of days a case returned from CAVC respects the affinity to the judge who authored a decision before distributing the appeal to any available judge. This does not include Legacy CAVC Remand Appeals with a hearing held.", + data_type: Constants.ACD_LEVERS.data_types.radio, + value: "21", + unit: Constants.ACD_LEVERS.days, + options: [ + { + item: Constants.ACD_LEVERS.value, + data_type: Constants.ACD_LEVERS.data_types.number, + value: 21, + text: "Attempt distribution to current judge for max of:", + unit: Constants.ACD_LEVERS.days, + selected: true + }, + { + item: Constants.ACD_LEVERS.infinite, + value: Constants.ACD_LEVERS.infinite, + text: "Always distribute to current judge" + }, + { + item: Constants.ACD_LEVERS.omit, + value: Constants.ACD_LEVERS.omit, + text: "Omit variable from distribution rules" + } + ], + is_disabled_in_ui: true, + min_value: 0, + max_value: 999, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket, Constants.ACD_LEVERS.algorithms.proportion], + lever_group: Constants.ACD_LEVERS.lever_groups.affinity, + lever_group_order: 3002 + }, + { + item: Constants.DISTRIBUTION.cavc_aod_affinity_days, + title: Constants.DISTRIBUTION.cavc_aod_affinity_days_title, + description: "Sets the number of days appeals returned from CAVC that are also AOD respect the affinity to the deciding judge. This is not applicable for legacy apeals for which the deciding judge conducted the most recent hearing.", + data_type: Constants.ACD_LEVERS.data_types.radio, + value: "14", + unit: Constants.ACD_LEVERS.days, + options: [ + { + item: Constants.ACD_LEVERS.value, + data_type: Constants.ACD_LEVERS.data_types.number, + value: 14, + text: "Attempt distribution to current judge for max of:", + unit: Constants.ACD_LEVERS.days, + selected: true + }, + { + item: Constants.ACD_LEVERS.infinite, + value: Constants.ACD_LEVERS.infinite, + text: "Always distribute to current judge" + }, + { + item: Constants.ACD_LEVERS.omit, + value: Constants.ACD_LEVERS.omit, + text: "Omit variable from distribution rules" + } + ], + is_disabled_in_ui: true, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion], + lever_group: Constants.ACD_LEVERS.lever_groups.affinity, + lever_group_order: 3003 + }, + { + item: Constants.DISTRIBUTION.aoj_affinity_days, + title: Constants.DISTRIBUTION.aoj_affinity_days_title, + description: "Sets the number of days an appeal respects the affinity to the deciding judge for Legacy AOJ Remand Returned appeals with no hearing held before distributing the appeal to any available judge.", + data_type: Constants.ACD_LEVERS.data_types.radio, + value: "60", + unit: Constants.ACD_LEVERS.days, + options: [ + { + item: Constants.ACD_LEVERS.value, + data_type: Constants.ACD_LEVERS.data_types.number, + value: 60, + text: "Attempt distribution to current judge for max of:", + unit: Constants.ACD_LEVERS.days, + selected: true + }, + { + item: Constants.ACD_LEVERS.infinite, + data_type: "", + value: Constants.ACD_LEVERS.infinite, + text: "Always distribute to current judge", + unit: "" + }, + { + item: Constants.ACD_LEVERS.omit, + data_type: "", + value: Constants.ACD_LEVERS.omit, + text: "Omit variable from distribution rules", + unit: "" + } + ], + is_disabled_in_ui: true, + min_value: 0, + max_value: 999, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.affinity, + lever_group_order: 3004 + }, + { + item: Constants.DISTRIBUTION.aoj_aod_affinity_days, + title: Constants.DISTRIBUTION.aoj_aod_affinity_days_title, + description: "Sets the number of days legacy remand Returned appeals that are also AOD (and may or may not have been CAVC at one time) respect the affinity before distributing the appeal to any available jduge. Affects appeals with hearing held when the remanding judge is not the hearing judge, or any legacy AOD + AOD appeal with no hearing held (whether or not it had been CAVC at one time).", + data_type: Constants.ACD_LEVERS.data_types.radio, + value: "14", + unit: Constants.ACD_LEVERS.days, + options: [ + { + item: Constants.ACD_LEVERS.value, + data_type: Constants.ACD_LEVERS.data_types.number, + value: 14, + text: "Attempt distribution to current judge for max of:", + unit: Constants.ACD_LEVERS.days, + selected: true + }, + { + item: Constants.ACD_LEVERS.infinite, + data_type: "", + value: Constants.ACD_LEVERS.infinite, + text: "Always distribute to current judge", + unit: "" + }, + { + item: Constants.ACD_LEVERS.omit, + data_type: "", + value: Constants.ACD_LEVERS.omit, + text: "Omit variable from distribution rules", + unit: "" + } + ], + is_disabled_in_ui: true, + min_value: 0, + max_value: 999, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.affinity, + lever_group_order: 3005 + }, + { + item: Constants.DISTRIBUTION.aoj_cavc_affinity_days, + title: Constants.DISTRIBUTION.aoj_cavc_affinity_days_title, + description: "Sets the number of days AOJ appeals that were CAVC at some time respect the affinity before the appeal is distributed to any available judge. This applies to any AOJ + CAVC appeal with no hearing held, or those with a hearing held when the remanding judge is not the hearing judge.", + data_type: Constants.ACD_LEVERS.data_types.radio, + value: "21", + unit: Constants.ACD_LEVERS.days, + options: [ + { + item: Constants.ACD_LEVERS.value, + data_type: Constants.ACD_LEVERS.data_types.number, + value: 21, + text: "Attempt distribution to current judge for max of:", + unit: Constants.ACD_LEVERS.days, + selected: true + }, + { + item: Constants.ACD_LEVERS.infinite, + data_type: "", + value: Constants.ACD_LEVERS.infinite, + text: "Always distribute to current judge", + unit: "" + }, + { + item: Constants.ACD_LEVERS.omit, + data_type: "", + value: Constants.ACD_LEVERS.omit, + text: "Omit variable from distribution rules", + unit: "" + } + ], + is_disabled_in_ui: true, + min_value: 0, + max_value: 999, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.affinity, + lever_group_order: 3006 + }, + { + item: Constants.DISTRIBUTION.ama_hearings_start_distribution_prior_to_goals, + title: "AMA Hearings Start Distribution Prior to Goals", + description: "", + data_type: Constants.ACD_LEVERS.data_types.combination, + value: 60, + unit: Constants.ACD_LEVERS.days, + options: [ + { + item: Constants.ACD_LEVERS.value, + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: true, + text: "This feature is turned on or off", + unit: "" + } + ], + is_toggle_active: false, + is_disabled_in_ui: true, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_distribution_prior, + lever_group_order: 4000 + }, + { + item: Constants.DISTRIBUTION.ama_direct_review_start_distribution_prior_to_goals, + title: "AMA Direct Review Start Distribution Prior to Goals", + description: "", + data_type: Constants.ACD_LEVERS.data_types.combination, + value: 365, + unit: Constants.ACD_LEVERS.days, + options: [ + { + item: Constants.ACD_LEVERS.value, + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: true, + text: "This feature is turned on or off", + unit: "" + } + ], + is_toggle_active: false, + is_disabled_in_ui: false, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_distribution_prior, + lever_group_order: 4001 + }, + { + item: Constants.DISTRIBUTION.ama_evidence_submission_start_distribution_prior_to_goals, + title: "AMA Evidence Submission Start Distribution Prior to Goals", + description: "", + data_type: Constants.ACD_LEVERS.data_types.combination, + value: 60, + unit: Constants.ACD_LEVERS.days, + options: [ + { + item: Constants.ACD_LEVERS.value, + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: true, + text: "This feature is turned on or off", + unit: "" + } + ], + is_toggle_active: false, + is_disabled_in_ui: true, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_distribution_prior, + lever_group_order: 4002 + }, + { + item: Constants.DISTRIBUTION.ama_hearings_docket_time_goals, + title: "AMA Hearings Docket Time Goals", + data_type: Constants.ACD_LEVERS.data_types.number, + value: 730, + unit: Constants.ACD_LEVERS.days, + is_disabled_in_ui: true, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_time_goal, + lever_group_order: 4003 + }, + { + item: Constants.DISTRIBUTION.ama_direct_review_docket_time_goals, + title: "AMA Direct Review Docket Time Goals", + data_type: Constants.ACD_LEVERS.data_types.number, + value: 365, + unit: Constants.ACD_LEVERS.days, + is_disabled_in_ui: false, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_time_goal, + lever_group_order: 4004 + }, + { + item: Constants.DISTRIBUTION.ama_evidence_submission_docket_time_goals, + title: "AMA Evidence Submission Docket Time Goals", + data_type: Constants.ACD_LEVERS.data_types.number, + value: 550, + unit: Constants.ACD_LEVERS.days, + is_disabled_in_ui: true, + min_value: 0, + max_value: nil, + algorithms_used: [Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_time_goal, + lever_group_order: 4005 + }, + { + item: Constants.DISTRIBUTION.disable_legacy_priority, + title: Constants.DISTRIBUTION.disable_legacy_priority_title, + description: "", + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: false, + unit: "", + is_disabled_in_ui: true, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, + lever_group_order: 10 + }, + { + item: Constants.DISTRIBUTION.disable_legacy_non_priority, + title: Constants.DISTRIBUTION.disable_legacy_non_priority_title, + description: "", + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: false, + unit: "", + is_disabled_in_ui: true, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, + lever_group_order: 101 + }, + { + item: Constants.DISTRIBUTION.disable_ama_non_priority_hearing, + title: Constants.DISTRIBUTION.disable_ama_non_priority_hearing_title, + description: "", + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: false, + unit: "", + is_disabled_in_ui: true, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, + lever_group_order: 102 + }, + { + item: Constants.DISTRIBUTION.disable_ama_non_priority_direct_review, + title: Constants.DISTRIBUTION.disable_ama_non_priority_direct_review_title, + description: "", + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: false, + unit: "", + is_disabled_in_ui: true, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, + lever_group_order: 103 + }, + { + item: Constants.DISTRIBUTION.disable_ama_non_priority_evidence_sub, + title: Constants.DISTRIBUTION.disable_ama_non_priority_evidence_sub_title, + description: "", + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: false, + unit: "", + is_disabled_in_ui: true, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, + lever_group_order: 104 + }, + { + item: Constants.DISTRIBUTION.disable_ama_priority_hearing, + title: Constants.DISTRIBUTION.disable_ama_priority_hearing_title, + description: "", + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: false, + unit: "", + is_disabled_in_ui: true, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, + lever_group_order: 105 + }, + { + item: Constants.DISTRIBUTION.disable_ama_priority_direct_review, + title: Constants.DISTRIBUTION.disable_ama_priority_direct_review_title, + description: "", + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: false, + unit: "", + is_disabled_in_ui: true, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, + lever_group_order: 106 + }, + { + item: Constants.DISTRIBUTION.disable_ama_priority_evidence_sub, + title: Constants.DISTRIBUTION.disable_ama_priority_evidence_sub_title, + description: "", + data_type: Constants.ACD_LEVERS.data_types.boolean, + value: false, + unit: "", + is_disabled_in_ui: true, + algorithms_used: [Constants.ACD_LEVERS.algorithms.proportion, Constants.ACD_LEVERS.algorithms.docket], + lever_group: Constants.ACD_LEVERS.lever_groups.docket_levers, + lever_group_order: 107 + } + ] + end + + # DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER + # + # This is a DANGEROUS OPERATION and only should be done when a lever needs to be completely updated + # + # Can pass in a Constants.ACD_LEVERS.data_types or a lever's item value + # + # If passing in Constants.ACD_LEVERS.data_types it will fully update all levers with that data_type + # + # DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER + def full_update(item) + levers_to_update = [] + levers_to_update = if Constants.ACD_LEVERS.data_types.to_h.value?(item) + levers.filter { |lever| lever[:data_type] == item } + else + levers.filter { |lever| lever[:item] == item } + end + + levers_to_update.each do |lever| + full_update_lever(lever) + end + + puts "Levers updated: #{levers_to_update.map { |lever| lever[:item] }}" + end + + private + + # DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER + # + # Doesn't update item + # + # Updates all fields of a lever + # + # This is a DANGEROUS OPERATION and only should be used when a lever needs to be completely updated + # + # DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER DANGER + def full_update_lever(lever) + existing_lever = CaseDistributionLever.find_by_item(lever[:item]) + + existing_lever.update( + title: lever[:title], + description: lever[:description], + data_type: lever[:data_type], + value: lever[:value].to_s, + unit: lever[:unit], + is_toggle_active: lever[:is_toggle_active], + is_disabled_in_ui: lever[:is_disabled_in_ui] || false, + min_value: lever[:min_value], + max_value: lever[:max_value], + algorithms_used: lever[:algorithms_used], + options: lever[:options], + control_group: lever[:control_group], + lever_group: lever[:lever_group], + lever_group_order: lever[:lever_group_order] + ) + + puts "*********************************************" + puts existing_lever.errors.full_messages unless existing_lever.valid? + puts "*********************************************" + end end end end diff --git a/spec/controllers/judge_assign_tasks_controller_spec.rb b/spec/controllers/judge_assign_tasks_controller_spec.rb index b1621703922..bf7ae139234 100644 --- a/spec/controllers/judge_assign_tasks_controller_spec.rb +++ b/spec/controllers/judge_assign_tasks_controller_spec.rb @@ -110,10 +110,11 @@ let!(:assignee) { second_judge } it "raises an error" do - expect { subject }.to raise_error do |error| - expect(error).to be_a(ActiveRecord::RecordInvalid) - expect(error.message).to eq("Validation failed: Assigned to has to be an attorney") - end + subject + + expect(response.status).to eq 400 + response_body = JSON.parse(response.body)["errors"].first["detail"] + expect(response_body).to eq "The selected individual is not an attorney in VACOLS" end end end diff --git a/spec/controllers/post_decision_motions_controller_spec.rb b/spec/controllers/post_decision_motions_controller_spec.rb index 162dd979665..f212e63f0d9 100644 --- a/spec/controllers/post_decision_motions_controller_spec.rb +++ b/spec/controllers/post_decision_motions_controller_spec.rb @@ -53,7 +53,8 @@ expect(body["errors"]).to match_array( [{ "detail" => - "Assigned by has to be a judge or special case movement team member, Assigned to has to be an attorney" + "Assigned by has to be a judge or special case movement team member, "\ + "The selected individual is not an attorney in VACOLS" }] ) end diff --git a/spec/models/case_distribution_lever_spec.rb b/spec/models/case_distribution_lever_spec.rb index 731b9bb89c7..52991e1319b 100644 --- a/spec/models/case_distribution_lever_spec.rb +++ b/spec/models/case_distribution_lever_spec.rb @@ -109,19 +109,6 @@ end end - context "distribution_value" do - it "should return value from options value when radio data type lever object" do - lever = CaseDistributionLever.find_by_item(Constants.DISTRIBUTION.ama_hearing_case_affinity_days) - option = lever.options.detect { |opt| opt["item"] == lever.value } - expect(lever.distribution_value).to eq(option["value"]) - end - - it "should return value from lever object" do - lever = CaseDistributionLever.find_by_item(Constants.DISTRIBUTION.request_more_cases_minimum) - expect(lever.distribution_value).to eq(lever.value) - end - end - context "update_acd_levers" do it "makes valid lever updates and creates audit entries" do request_more_cases_minimum = CaseDistributionLever.find_by_item(Constants.DISTRIBUTION.request_more_cases_minimum) diff --git a/spec/models/tasks/attorney_task_spec.rb b/spec/models/tasks/attorney_task_spec.rb index 3006828269b..39b8ebbcad0 100644 --- a/spec/models/tasks/attorney_task_spec.rb +++ b/spec/models/tasks/attorney_task_spec.rb @@ -48,7 +48,7 @@ let!(:attorney_staff) { create(:staff, sdomainid: attorney.css_id, sattyid: nil) } it "fails" do expect(subject).to be_invalid - expect(subject.errors.messages[:assigned_to].first).to eq "has to be an attorney" + expect(subject.errors.messages[:base].first).to eq "The selected individual is not an attorney in VACOLS" end end