diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 61bd52e4f5c..174b77f4df9 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -7241,6 +7241,11 @@ func init() { "grade": { "$ref": "#/definitions/Grade" }, + "hasDependents": { + "type": "boolean", + "title": "Are dependents included in your orders?", + "x-nullable": true + }, "issueDate": { "description": "The date and time that these orders were cut.", "type": "string", @@ -24447,6 +24452,11 @@ func init() { "grade": { "$ref": "#/definitions/Grade" }, + "hasDependents": { + "type": "boolean", + "title": "Are dependents included in your orders?", + "x-nullable": true + }, "issueDate": { "description": "The date and time that these orders were cut.", "type": "string", diff --git a/pkg/gen/ghcmessages/counseling_update_order_payload.go b/pkg/gen/ghcmessages/counseling_update_order_payload.go index a03a99a22de..281972b5196 100644 --- a/pkg/gen/ghcmessages/counseling_update_order_payload.go +++ b/pkg/gen/ghcmessages/counseling_update_order_payload.go @@ -26,6 +26,9 @@ type CounselingUpdateOrderPayload struct { // grade Grade *Grade `json:"grade,omitempty"` + // Are dependents included in your orders? + HasDependents *bool `json:"hasDependents,omitempty"` + // Orders date // // The date and time that these orders were cut. diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index e2c02f30c06..175d734c2f4 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -682,6 +682,7 @@ func Order(order *models.Order) *ghcmessages.Order { MoveCode: moveCode, MoveTaskOrderID: moveTaskOrderID, OriginDutyLocationGBLOC: ghcmessages.GBLOC(swag.StringValue(order.OriginDutyLocationGBLOC)), + HasDependents: order.HasDependents, } return &payload diff --git a/pkg/services/order/order_updater.go b/pkg/services/order/order_updater.go index 38c991a112a..02b8c848ca5 100644 --- a/pkg/services/order/order_updater.go +++ b/pkg/services/order/order_updater.go @@ -400,6 +400,10 @@ func orderFromCounselingPayload(existingOrder models.Order, payload ghcmessages. order.Entitlement.DBAuthorizedWeight = &weight } + if payload.HasDependents != nil { + order.HasDependents = *payload.HasDependents + } + return order } diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.jsx index 727e47e256c..f934938ca22 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.jsx @@ -13,7 +13,7 @@ import ToolTip from 'shared/ToolTip/ToolTip'; import { DatePickerInput, DropdownInput, DutyLocationInput } from 'components/form/fields'; import { Form } from 'components/form/Form'; import SectionWrapper from 'components/Customer/SectionWrapper'; -import { ORDERS_PAY_GRADE_OPTIONS } from 'constants/orders'; +import { ORDERS_PAY_GRADE_OPTIONS, ORDERS_TYPE } from 'constants/orders'; import { dropdownInputOptions } from 'utils/formatters'; import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; import Callout from 'components/Callout'; @@ -39,6 +39,9 @@ const AddOrdersForm = ({ const [hasDependents, setHasDependents] = useState(false); const [isOconusMove, setIsOconusMove] = useState(false); const [enableUB, setEnableUB] = useState(false); + const [isHasDependentsDisabled, setHasDependentsDisabled] = useState(false); + const [prevOrderType, setPrevOrderType] = useState(''); + const [filteredOrderTypeOptions, setFilteredOrderTypeOptions] = useState(ordersTypeOptions); const validationSchema = Yup.object().shape({ ordersType: Yup.mixed() @@ -95,9 +98,24 @@ const AddOrdersForm = ({ } }, [currentDutyLocation, newDutyLocation, isOconusMove, hasDependents, enableUB]); + useEffect(() => { + const fetchData = async () => { + const alaskaEnabled = await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.ENABLE_ALASKA); + + const updatedOptions = alaskaEnabled + ? ordersTypeOptions + : ordersTypeOptions.filter( + (e) => e.key !== ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS && e.key !== ORDERS_TYPE.STUDENT_TRAVEL, + ); + + setFilteredOrderTypeOptions(updatedOptions); + }; + fetchData(); + }, [ordersTypeOptions]); + return ( - {({ values, isValid, isSubmitting, handleSubmit, touched, setFieldValue }) => { + {({ values, isValid, isSubmitting, handleSubmit, handleChange, touched, setFieldValue }) => { const isRetirementOrSeparation = ['RETIREMENT', 'SEPARATION'].includes(values.ordersType); if (!values.origin_duty_location && touched.origin_duty_location) originMeta = 'Required'; else originMeta = null; @@ -107,17 +125,37 @@ const AddOrdersForm = ({ const handleHasDependentsChange = (e) => { // Declare a duplicate local scope of the field value // for the form to prevent state race conditions - const fieldValueHasDependents = e.target.value === 'yes'; - setHasDependents(e.target.value === 'yes'); - setFieldValue('hasDependents', fieldValueHasDependents ? 'yes' : 'no'); - if (fieldValueHasDependents && isOconusMove && enableUB) { - setShowAccompaniedTourField(true); - setShowDependentAgeFields(true); + if (e.target.value === '') { + setFieldValue('hasDependents', ''); } else { - setShowAccompaniedTourField(false); - setShowDependentAgeFields(false); + const fieldValueHasDependents = e.target.value === 'yes'; + setHasDependents(e.target.value === 'yes'); + setFieldValue('hasDependents', fieldValueHasDependents ? 'yes' : 'no'); + if (fieldValueHasDependents && isOconusMove && enableUB) { + setShowAccompaniedTourField(true); + setShowDependentAgeFields(true); + } else { + setShowAccompaniedTourField(false); + setShowDependentAgeFields(false); + } } }; + const handleOrderTypeChange = (e) => { + const { value } = e.target; + if (value === ORDERS_TYPE.STUDENT_TRAVEL || value === ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS) { + setHasDependentsDisabled(true); + handleHasDependentsChange({ target: { value: 'yes' } }); + } else { + setHasDependentsDisabled(false); + if ( + prevOrderType === ORDERS_TYPE.STUDENT_TRAVEL || + prevOrderType === ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS + ) { + handleHasDependentsChange({ target: { value: '' } }); + } + } + setPrevOrderType(value); + }; return (
@@ -127,8 +165,12 @@ const AddOrdersForm = ({ { + handleChange(e); + handleOrderTypeChange(e); + }} isDisabled={isSafetyMoveSelected || isBluebarkMoveSelected} /> @@ -206,6 +248,7 @@ const AddOrdersForm = ({ onChange={(e) => { handleHasDependentsChange(e); }} + disabled={isHasDependentsDisabled} /> { handleHasDependentsChange(e); }} + disabled={isHasDependentsDisabled} /> diff --git a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx index f024dbfbc78..3692f7432ee 100644 --- a/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx +++ b/src/components/Office/AddOrdersForm/AddOrdersForm.test.jsx @@ -6,7 +6,7 @@ import { Provider } from 'react-redux'; import AddOrdersForm from './AddOrdersForm'; import { dropdownInputOptions } from 'utils/formatters'; -import { ORDERS_TYPE_OPTIONS } from 'constants/orders'; +import { ORDERS_TYPE, ORDERS_TYPE_OPTIONS } from 'constants/orders'; import { configureStore } from 'shared/store'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; @@ -126,6 +126,40 @@ describe('CreateMoveCustomerInfo Component', () => { }); }); + it('renders each option for orders type', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + const { getByLabelText } = render( + + + , + ); + + const ordersTypeDropdown = getByLabelText('Orders type'); + expect(ordersTypeDropdown).toBeInstanceOf(HTMLSelectElement); + + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); + expect(ordersTypeDropdown).toHaveValue(ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); + + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.LOCAL_MOVE); + expect(ordersTypeDropdown).toHaveValue(ORDERS_TYPE.LOCAL_MOVE); + + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.RETIREMENT); + expect(ordersTypeDropdown).toHaveValue(ORDERS_TYPE.RETIREMENT); + + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.SEPARATION); + expect(ordersTypeDropdown).toHaveValue(ORDERS_TYPE.SEPARATION); + + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.TEMPORARY_DUTY); + expect(ordersTypeDropdown).toHaveValue(ORDERS_TYPE.TEMPORARY_DUTY); + + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + expect(ordersTypeDropdown).toHaveValue(ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.STUDENT_TRAVEL); + expect(ordersTypeDropdown).toHaveValue(ORDERS_TYPE.STUDENT_TRAVEL); + }); + it('shows an error message if trying to submit an invalid form', async () => { const { getByRole, findAllByRole, getByLabelText } = render( @@ -188,6 +222,7 @@ describe('AddOrdersForm - OCONUS and Accompanied Tour Test', () => { }); }); }); + describe('AddOrdersForm - Edge Cases and Additional Scenarios', () => { it('disables orders type when safety move is selected', async () => { render( @@ -208,3 +243,140 @@ describe('AddOrdersForm - Edge Cases and Additional Scenarios', () => { expect(screen.getByLabelText('Orders type')).toBeDisabled(); }); }); + +describe('AddOrdersForm - Student Travel, Early Return of Dependents Test', () => { + it('has dependents is yes and disabled when order type is student travel', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + render( + + + , + ); + + await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.STUDENT_TRAVEL); + + const hasDependentsYes = screen.getByLabelText('Yes'); + const hasDependentsNo = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYes).toBeChecked(); + expect(hasDependentsYes).toBeDisabled(); + expect(hasDependentsNo).toBeDisabled(); + }); + }); + + it('has dependents is yes and disabled when order type is early return', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + render( + + + , + ); + + await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + const hasDependentsYes = screen.getByLabelText('Yes'); + const hasDependentsNo = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYes).toBeChecked(); + expect(hasDependentsYes).toBeDisabled(); + expect(hasDependentsNo).toBeDisabled(); + }); + }); + + it('has dependents becomes disabled and then re-enabled for order type student travel', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + render( + + + , + ); + + await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); + + const hasDependentsYesPermChg = screen.getByLabelText('Yes'); + const hasDependentsNoPermChg = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYesPermChg).not.toBeChecked(); + expect(hasDependentsYesPermChg).toBeEnabled(); + expect(hasDependentsNoPermChg).not.toBeChecked(); + expect(hasDependentsNoPermChg).toBeEnabled(); + }); + + // set order type to value that disables and defaults "has dependents" + await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.STUDENT_TRAVEL); + + const hasDependentsYesStudent = screen.getByLabelText('Yes'); + const hasDependentsNoStudent = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYesStudent).toBeChecked(); + expect(hasDependentsYesStudent).toBeDisabled(); + expect(hasDependentsNoStudent).toBeDisabled(); + }); + + // set order type to value the re-enables "has dependents" + await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.LOCAL_MOVE); + + const hasDependentsYesLocalMove = screen.getByLabelText('Yes'); + const hasDependentsNoLocalMove = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYesLocalMove).not.toBeChecked(); + expect(hasDependentsYesLocalMove).toBeEnabled(); + expect(hasDependentsNoLocalMove).not.toBeChecked(); + expect(hasDependentsNoLocalMove).toBeEnabled(); + }); + }); + + it('has dependents becomes disabled and then re-enabled for order type early return', async () => { + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + render( + + + , + ); + + await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); + + const hasDependentsYesPermChg = screen.getByLabelText('Yes'); + const hasDependentsNoPermChg = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYesPermChg).not.toBeChecked(); + expect(hasDependentsYesPermChg).toBeEnabled(); + expect(hasDependentsNoPermChg).not.toBeChecked(); + expect(hasDependentsNoPermChg).toBeEnabled(); + }); + + // set order type to value that disables and defaults "has dependents" + await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + + const hasDependentsYesEarly = screen.getByLabelText('Yes'); + const hasDependentsNoEarly = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYesEarly).toBeChecked(); + expect(hasDependentsYesEarly).toBeDisabled(); + expect(hasDependentsNoEarly).toBeDisabled(); + }); + + // set order type to value the re-enables "has dependents" + await userEvent.selectOptions(screen.getByLabelText('Orders type'), ORDERS_TYPE.LOCAL_MOVE); + + const hasDependentsYesLocalMove = screen.getByLabelText('Yes'); + const hasDependentsNoLocalMove = screen.getByLabelText('No'); + + await waitFor(() => { + expect(hasDependentsYesLocalMove).not.toBeChecked(); + expect(hasDependentsYesLocalMove).toBeEnabled(); + expect(hasDependentsNoLocalMove).not.toBeChecked(); + expect(hasDependentsNoLocalMove).toBeEnabled(); + }); + }); +}); diff --git a/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.jsx b/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.jsx index 9a4b611f0de..5a3d37c59e0 100644 --- a/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.jsx +++ b/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.jsx @@ -1,5 +1,5 @@ /* eslint-disable camelcase */ -import React, { useEffect, useReducer } from 'react'; +import React, { useEffect, useReducer, useState } from 'react'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { Button } from '@trussworks/react-uswds'; import { Formik } from 'formik'; @@ -8,27 +8,32 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import ordersFormValidationSchema from '../Orders/ordersFormValidationSchema'; +import { isBooleanFlagEnabled } from 'utils/featureFlags'; import styles from 'styles/documentViewerWithSidebar.module.scss'; import { milmoveLogger } from 'utils/milmoveLog'; import OrdersDetailForm from 'components/Office/OrdersDetailForm/OrdersDetailForm'; import { DEPARTMENT_INDICATOR_OPTIONS } from 'constants/departmentIndicators'; -import { ORDERS_TYPE_DETAILS_OPTIONS, ORDERS_TYPE_OPTIONS, ORDERS_PAY_GRADE_OPTIONS } from 'constants/orders'; +import { + ORDERS_TYPE_DETAILS_OPTIONS, + ORDERS_TYPE_OPTIONS, + ORDERS_PAY_GRADE_OPTIONS, + ORDERS_TYPE, +} from 'constants/orders'; import { ORDERS } from 'constants/queryKeys'; import { servicesCounselingRoutes } from 'constants/routes'; import { useOrdersDocumentQueries } from 'hooks/queries'; import { getTacValid, getLoa, counselingUpdateOrder, getOrder } from 'services/ghcApi'; -import { formatSwaggerDate, dropdownInputOptions } from 'utils/formatters'; +import { formatSwaggerDate, dropdownInputOptions, formatYesNoAPIValue } from 'utils/formatters'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; import { LineOfAccountingDfasElementOrder } from 'types/lineOfAccounting'; import { LOA_VALIDATION_ACTIONS, reducer as loaReducer, initialState as initialLoaState } from 'reducers/loaValidation'; import { TAC_VALIDATION_ACTIONS, reducer as tacReducer, initialState as initialTacState } from 'reducers/tacValidation'; -import { LOA_TYPE, MOVE_DOCUMENT_TYPE } from 'shared/constants'; +import { LOA_TYPE, MOVE_DOCUMENT_TYPE, FEATURE_FLAG_KEYS } from 'shared/constants'; import DocumentViewerFileManager from 'components/DocumentViewerFileManager/DocumentViewerFileManager'; import { scrollToViewFormikError } from 'utils/validation'; const deptIndicatorDropdownOptions = dropdownInputOptions(DEPARTMENT_INDICATOR_OPTIONS); -const ordersTypeDropdownOptions = dropdownInputOptions(ORDERS_TYPE_OPTIONS); const ordersTypeDetailsDropdownOptions = dropdownInputOptions(ORDERS_TYPE_DETAILS_OPTIONS); const payGradeDropdownOptions = dropdownInputOptions(ORDERS_PAY_GRADE_OPTIONS); @@ -39,8 +44,10 @@ const ServicesCounselingOrders = ({ files, amendedDocumentId, updateAmendedDocum const [tacValidationState, tacValidationDispatch] = useReducer(tacReducer, null, initialTacState); const [loaValidationState, loaValidationDispatch] = useReducer(loaReducer, null, initialLoaState); const { move, orders, isLoading, isError } = useOrdersDocumentQueries(moveCode); + const [orderTypeOptions, setOrderTypeOptions] = useState(ORDERS_TYPE_OPTIONS); const orderId = move?.ordersId; + const initialValueOfHasDependents = orders[orderId]?.has_dependents; const orderDocumentId = orders[orderId]?.uploaded_order_id; const amendedOrderDocumentId = orders[orderId]?.uploadedAmendedOrderID || amendedDocumentId; @@ -247,6 +254,19 @@ const ServicesCounselingOrders = ({ files, amendedDocumentId, updateAmendedDocum validateLoa, ]); + useEffect(() => { + const checkAlaskaFeatureFlag = async () => { + const isAlaskaEnabled = await isBooleanFlagEnabled(FEATURE_FLAG_KEYS.ENABLE_ALASKA); + if (!isAlaskaEnabled) { + const options = orderTypeOptions; + delete orderTypeOptions.EARLY_RETURN_OF_DEPENDENTS; + delete orderTypeOptions.STUDENT_TRAVEL; + setOrderTypeOptions(options); + } + }; + checkAlaskaFeatureFlag(); + }, [orderTypeOptions]); + if (isLoading) return ; if (isError) return ; @@ -264,6 +284,10 @@ const ServicesCounselingOrders = ({ files, amendedDocumentId, updateAmendedDocum reportByDate: formatSwaggerDate(values.reportByDate), ordersType: values.ordersType, grade: values.payGrade, + hasDependents: + values.ordersType === ORDERS_TYPE.STUDENT_TRAVEL || values.ordersType === ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS + ? formatYesNoAPIValue('yes') + : initialValueOfHasDependents, }; mutateOrders({ orderID: orderId, ifMatchETag: newOrderEtag, body: orderBody }); }; @@ -292,6 +316,8 @@ const ServicesCounselingOrders = ({ files, amendedDocumentId, updateAmendedDocum 'Unable to find a LOA based on the provided details. Please ensure a department indicator and TAC are present on this form.'; const loaInvalidWarningMsg = 'The LOA identified based on the provided details appears to be invalid.'; + const ordersTypeDropdownOptions = dropdownInputOptions(orderTypeOptions); + return (
diff --git a/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.test.jsx b/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.test.jsx index c6d994b9a84..b10032c6da9 100644 --- a/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.test.jsx +++ b/src/pages/Office/ServicesCounselingOrders/ServicesCounselingOrders.test.jsx @@ -7,6 +7,9 @@ import ServicesCounselingOrders from 'pages/Office/ServicesCounselingOrders/Serv import { MockProviders } from 'testUtils'; import { useOrdersDocumentQueries } from 'hooks/queries'; import { MOVE_DOCUMENT_TYPE } from 'shared/constants'; +import { counselingUpdateOrder, getOrder } from 'services/ghcApi'; +import { formatYesNoAPIValue } from 'utils/formatters'; +import { ORDERS_TYPE } from 'constants/orders'; const mockOriginDutyLocation = { address: { @@ -100,6 +103,13 @@ jest.mock('services/ghcApi', () => ({ // Default to no LOA return Promise.resolve(undefined); }, + counselingUpdateOrder: jest.fn(), + getOrder: jest.fn(), +})); + +jest.mock('utils/featureFlags', () => ({ + ...jest.requireActual('utils/featureFlags'), + isBooleanFlagEnabled: jest.fn().mockImplementation(() => Promise.resolve(true)), })); const useOrdersDocumentQueriesReturnValue = { @@ -218,6 +228,8 @@ describe('Orders page', () => { }); it('renders each option for orders type dropdown', async () => { + useOrdersDocumentQueries.mockReturnValue(useOrdersDocumentQueriesReturnValue); + render( @@ -238,6 +250,12 @@ describe('Orders page', () => { await userEvent.selectOptions(ordersTypeDropdown, 'SEPARATION'); expect(ordersTypeDropdown).toHaveValue('SEPARATION'); + + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + expect(ordersTypeDropdown).toHaveValue(ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.STUDENT_TRAVEL); + expect(ordersTypeDropdown).toHaveValue(ORDERS_TYPE.STUDENT_TRAVEL); }); it('populates initial field values', async () => { @@ -409,6 +427,7 @@ describe('Orders page', () => { }); }); }); + describe('LOA concatenation', () => { it('concatenates the LOA string correctly', async () => { useOrdersDocumentQueries.mockReturnValue(useOrdersDocumentQueriesReturnValue); @@ -430,6 +449,7 @@ describe('Orders page', () => { expect(loaTextField).toHaveValue(expectedLongLineOfAccounting); }); }); + describe('LOA concatenation with regex removes extra spaces', () => { it('concatenates the LOA string correctly and without extra spaces', async () => { let extraSpacesLongLineOfAccounting = @@ -446,4 +466,332 @@ describe('Orders page', () => { expect(extraSpacesLongLineOfAccounting).toEqual(expectedLongLineOfAccounting); }); }); + + describe('Order type: STUDENT_TRAVEL', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('select STUDENT_TRAVEL', async () => { + // create a local copy of order return value and set initial values + const orderQueryReturnValues = JSON.parse(JSON.stringify(useOrdersDocumentQueriesReturnValue)); + orderQueryReturnValues.move = { id: 123, moveCode: 'GLOBAL123', ordersId: 1 }; + orderQueryReturnValues.orders[1].order_type = ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION; + orderQueryReturnValues.orders[1].has_dependents = formatYesNoAPIValue('no'); + + // set return values for mocked functions + useOrdersDocumentQueries.mockReturnValue(orderQueryReturnValues); + getOrder.mockResolvedValue(orderQueryReturnValues); + + // render component + render( + + + , + ); + + // Select STUDENT_TRAVEL from the dropdown + const ordersTypeDropdown = await screen.findByLabelText('Orders type'); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.STUDENT_TRAVEL); + + // Submit the form + const saveButton = screen.getByRole('button', { name: 'Save' }); + await userEvent.click(saveButton); + + // Verify correct values were passed + await waitFor(() => { + expect(counselingUpdateOrder).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + hasDependents: formatYesNoAPIValue('yes'), + ordersType: ORDERS_TYPE.STUDENT_TRAVEL, + }), + }), + ); + }); + }); + + it('De-select STUDENT_TRAVEL', async () => { + // create a local copy of order return value and set initial values + const orderQueryReturnValues = JSON.parse(JSON.stringify(useOrdersDocumentQueriesReturnValue)); + orderQueryReturnValues.move = { id: 123, moveCode: 'GLOBAL123', ordersId: 1 }; + orderQueryReturnValues.orders[1].order_type = ORDERS_TYPE.STUDENT_TRAVEL; + orderQueryReturnValues.orders[1].has_dependents = formatYesNoAPIValue('yes'); + + // set return values for mocked functions + useOrdersDocumentQueries.mockReturnValue(orderQueryReturnValues); + getOrder.mockResolvedValue(orderQueryReturnValues); + + // render component + render( + + + , + ); + + // De-select STUDENT_TRAVEL from the dropdown + const ordersTypeDropdown = await screen.findByLabelText('Orders type'); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); + + // Submit the form + const saveButton = screen.getByRole('button', { name: 'Save' }); + await userEvent.click(saveButton); + + // Verify correct values were passed + await waitFor(() => { + expect(counselingUpdateOrder).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + hasDependents: formatYesNoAPIValue('yes'), + ordersType: ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION, + }), + }), + ); + }); + }); + + it('select STUDENT_TRAVEL, De-select STUDENT_TRAVEL', async () => { + // create a local copy of order return value and set initial values + const orderQueryReturnValues = JSON.parse(JSON.stringify(useOrdersDocumentQueriesReturnValue)); + orderQueryReturnValues.move = { id: 123, moveCode: 'GLOBAL123', ordersId: 1 }; + orderQueryReturnValues.orders[1].order_type = ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION; + orderQueryReturnValues.orders[1].has_dependents = formatYesNoAPIValue('no'); + + // set return values for mocked functions + useOrdersDocumentQueries.mockReturnValue(orderQueryReturnValues); + getOrder.mockResolvedValue(orderQueryReturnValues); + + // render component + render( + + + , + ); + + // Select EARLY_RETURN_OF_DEPENDENTS and then de-select from the dropdown + const ordersTypeDropdown = await screen.findByLabelText('Orders type'); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.STUDENT_TRAVEL); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.LOCAL_MOVE); + + // Submit the form + const saveButton = screen.getByRole('button', { name: 'Save' }); + await userEvent.click(saveButton); + + // Verify correct values were passed + await waitFor(() => { + expect(counselingUpdateOrder).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + hasDependents: formatYesNoAPIValue('no'), + ordersType: ORDERS_TYPE.LOCAL_MOVE, + }), + }), + ); + }); + }); + + it('select STUDENT_TRAVEL, select EARLY_RETURN_OF_DEPENDENTS', async () => { + // create a local copy of order return value and set initial values + const orderQueryReturnValues = JSON.parse(JSON.stringify(useOrdersDocumentQueriesReturnValue)); + orderQueryReturnValues.move = { id: 123, moveCode: 'GLOBAL123', ordersId: 1 }; + orderQueryReturnValues.orders[1].order_type = ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION; + orderQueryReturnValues.orders[1].has_dependents = formatYesNoAPIValue('no'); + + // set return values for mocked functions + useOrdersDocumentQueries.mockReturnValue(orderQueryReturnValues); + getOrder.mockResolvedValue(orderQueryReturnValues); + + // render component + render( + + + , + ); + + // Select STUDENT_TRAVEL and then select EARLY_RETURN_OF_DEPENDENTS from the dropdown + const ordersTypeDropdown = await screen.findByLabelText('Orders type'); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.STUDENT_TRAVEL); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + + // Submit the form + const saveButton = screen.getByRole('button', { name: 'Save' }); + await userEvent.click(saveButton); + + // Verify correct values were passed + await waitFor(() => { + expect(counselingUpdateOrder).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + hasDependents: formatYesNoAPIValue('yes'), + ordersType: ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS, + }), + }), + ); + }); + }); + }); + + describe('Order type: EARLY_RETURN_OF_DEPENDENTS', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('select EARLY_RETURN_OF_DEPENDENTS', async () => { + // create a local copy of order return value and set initial values + const orderQueryReturnValues = JSON.parse(JSON.stringify(useOrdersDocumentQueriesReturnValue)); + orderQueryReturnValues.move = { id: 123, moveCode: 'GLOBAL123', ordersId: 1 }; + orderQueryReturnValues.orders[1].order_type = ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION; + orderQueryReturnValues.orders[1].has_dependents = formatYesNoAPIValue('no'); + + // set return values for mocked functions + useOrdersDocumentQueries.mockReturnValue(orderQueryReturnValues); + getOrder.mockResolvedValue(orderQueryReturnValues); + + // render component + render( + + + , + ); + + // Select EARLY_RETURN_OF_DEPENDENTS from the dropdown + const ordersTypeDropdown = await screen.findByLabelText('Orders type'); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + + // Submit the form + const saveButton = screen.getByRole('button', { name: 'Save' }); + await userEvent.click(saveButton); + + // Verify correct values were passed + await waitFor(() => { + expect(counselingUpdateOrder).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + hasDependents: formatYesNoAPIValue('yes'), + ordersType: ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS, + }), + }), + ); + }); + }); + + it('De-select EARLY_RETURN_OF_DEPENDENTS', async () => { + // create a local copy of order return value and set initial values + const orderQueryReturnValues = JSON.parse(JSON.stringify(useOrdersDocumentQueriesReturnValue)); + orderQueryReturnValues.move = { id: 123, moveCode: 'GLOBAL123', ordersId: 1 }; + orderQueryReturnValues.orders[1].order_type = ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS; + orderQueryReturnValues.orders[1].has_dependents = formatYesNoAPIValue('yes'); + + // set return values for mocked functions + useOrdersDocumentQueries.mockReturnValue(orderQueryReturnValues); + getOrder.mockResolvedValue(orderQueryReturnValues); + + // render component + render( + + + , + ); + + // De-select EARLY_RETURN_OF_DEPENDENTS from the dropdown + const ordersTypeDropdown = await screen.findByLabelText('Orders type'); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION); + + // Submit the form + const saveButton = screen.getByRole('button', { name: 'Save' }); + await userEvent.click(saveButton); + + // Verify correct values were passed + await waitFor(() => { + expect(counselingUpdateOrder).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + hasDependents: formatYesNoAPIValue('yes'), + ordersType: ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION, + }), + }), + ); + }); + }); + + it('select EARLY_RETURN_OF_DEPENDENTS, De-select EARLY_RETURN_OF_DEPENDENTS', async () => { + // create a local copy of order return value and set initial values + const orderQueryReturnValues = JSON.parse(JSON.stringify(useOrdersDocumentQueriesReturnValue)); + orderQueryReturnValues.move = { id: 123, moveCode: 'GLOBAL123', ordersId: 1 }; + orderQueryReturnValues.orders[1].order_type = ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION; + orderQueryReturnValues.orders[1].has_dependents = formatYesNoAPIValue('no'); + + // set return values for mocked functions + useOrdersDocumentQueries.mockReturnValue(orderQueryReturnValues); + getOrder.mockResolvedValue(orderQueryReturnValues); + + // render component + render( + + + , + ); + + // Select EARLY_RETURN_OF_DEPENDENTS and then de-select from the dropdown + const ordersTypeDropdown = await screen.findByLabelText('Orders type'); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.LOCAL_MOVE); + + // Submit the form + const saveButton = screen.getByRole('button', { name: 'Save' }); + await userEvent.click(saveButton); + + // Verify correct values were passed + await waitFor(() => { + expect(counselingUpdateOrder).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + hasDependents: formatYesNoAPIValue('no'), + ordersType: ORDERS_TYPE.LOCAL_MOVE, + }), + }), + ); + }); + }); + + it('select EARLY_RETURN_OF_DEPENDENTS, select STUDENT_TRAVEL', async () => { + // create a local copy of order return value and set initial values + const orderQueryReturnValues = JSON.parse(JSON.stringify(useOrdersDocumentQueriesReturnValue)); + orderQueryReturnValues.move = { id: 123, moveCode: 'GLOBAL123', ordersId: 1 }; + orderQueryReturnValues.orders[1].order_type = ORDERS_TYPE.PERMANENT_CHANGE_OF_STATION; + orderQueryReturnValues.orders[1].has_dependents = formatYesNoAPIValue('no'); + + // set return values for mocked functions + useOrdersDocumentQueries.mockReturnValue(orderQueryReturnValues); + getOrder.mockResolvedValue(orderQueryReturnValues); + + // render component + render( + + + , + ); + + // Select EARLY_RETURN_OF_DEPENDENTS and then select STUDENT_TRAVEL from the dropdown + const ordersTypeDropdown = await screen.findByLabelText('Orders type'); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.EARLY_RETURN_OF_DEPENDENTS); + await userEvent.selectOptions(ordersTypeDropdown, ORDERS_TYPE.STUDENT_TRAVEL); + + // Submit the form + const saveButton = screen.getByRole('button', { name: 'Save' }); + await userEvent.click(saveButton); + + // Verify correct values were passed + await waitFor(() => { + expect(counselingUpdateOrder).toHaveBeenCalledWith( + expect.objectContaining({ + body: expect.objectContaining({ + hasDependents: formatYesNoAPIValue('yes'), + ordersType: ORDERS_TYPE.STUDENT_TRAVEL, + }), + }), + ); + }); + }); + }); }); diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index e95349bcc3c..861742e4e35 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -5865,6 +5865,10 @@ definitions: $ref: definitions/NullableString.yaml grade: $ref: '#/definitions/Grade' + hasDependents: + type: boolean + title: Are dependents included in your orders? + x-nullable: true required: - issueDate - reportByDate diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 23c28fdb556..bb8b77d3a0f 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -6141,6 +6141,10 @@ definitions: $ref: '#/definitions/NullableString' grade: $ref: '#/definitions/Grade' + hasDependents: + type: boolean + title: Are dependents included in your orders? + x-nullable: true required: - issueDate - reportByDate