- DoD ID {customer.dodID}
+
+ DoD ID {customer.edipi}
{isCoastGuard && (
<>
diff --git a/src/components/Office/DefinitionLists/CustomerInfoList.jsx b/src/components/Office/DefinitionLists/CustomerInfoList.jsx
index b1b79d9ac01..16a698529f4 100644
--- a/src/components/Office/DefinitionLists/CustomerInfoList.jsx
+++ b/src/components/Office/DefinitionLists/CustomerInfoList.jsx
@@ -19,7 +19,7 @@ const CustomerInfoList = ({ customerInfo }) => {
DoD ID
- {customerInfo.dodId}
+ {customerInfo.edipi}
{customerInfo.agency === departmentIndicators.COAST_GUARD && (
@@ -81,7 +81,7 @@ const CustomerInfoList = ({ customerInfo }) => {
CustomerInfoList.propTypes = {
customerInfo: PropTypes.shape({
name: PropTypes.string,
- dodId: PropTypes.string,
+ edipi: PropTypes.string,
phone: PropTypes.string,
email: PropTypes.string,
currentAddress: AddressShape,
diff --git a/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx b/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx
index 18682850c78..d957be52b4f 100644
--- a/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx
+++ b/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx
@@ -6,7 +6,7 @@ import CustomerInfoList from './CustomerInfoList';
const info = {
name: 'Smith, Kerry',
agency: 'COAST_GUARD',
- dodId: '9999999999',
+ edipi: '9999999999',
emplid: '7777777',
phone: '999-999-9999',
altPhone: '888-888-8888',
diff --git a/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx b/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx
index 3dc4c9dadab..2d557e2c874 100644
--- a/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx
+++ b/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx
@@ -10,7 +10,7 @@ import 'styles/office.scss';
import TabNav from 'components/TabNav';
import { isBooleanFlagEnabled } from 'utils/featureFlags';
-const ServicesCounselingTabNav = ({ unapprovedShipmentCount = 0, moveCode }) => {
+const ServicesCounselingTabNav = ({ unapprovedShipmentCount = 0, missingOrdersInfoCount, moveCode }) => {
const [supportingDocsFF, setSupportingDocsFF] = React.useState(false);
React.useEffect(() => {
const fetchData = async () => {
@@ -19,6 +19,14 @@ const ServicesCounselingTabNav = ({ unapprovedShipmentCount = 0, moveCode }) =>
fetchData();
}, []);
+ let moveDetailsTagCount = 0;
+ if (unapprovedShipmentCount > 0) {
+ moveDetailsTagCount += unapprovedShipmentCount;
+ }
+ if (missingOrdersInfoCount > 0) {
+ moveDetailsTagCount += missingOrdersInfoCount;
+ }
+
const items = [
data-testid="MoveDetails-Tab"
>
Move details
- {unapprovedShipmentCount > 0 && {unapprovedShipmentCount}}
+ {moveDetailsTagCount > 0 && {moveDetailsTagCount}}
,
{
expect(within(moveDetailsTab).queryByTestId('tag')).not.toBeInTheDocument();
});
- it('should render the move details tab container with a tag that shows the count of unapproved shipments', () => {
+ it('should render the move details tab container with a tag that shows the count of action items', () => {
const moveDetailsShipmentAndAmendedOrders = {
...basicNavProps,
unapprovedShipmentCount: 6,
+ missingOrdersInfoCount: 4,
};
render(, { wrapper: MemoryRouter });
const moveDetailsTab = screen.getByTestId('MoveDetails-Tab');
- expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('6');
+ expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('10');
});
});
diff --git a/src/components/Office/TXOTabNav/TXOTabNav.jsx b/src/components/Office/TXOTabNav/TXOTabNav.jsx
index 9732c46407e..d3db9fbd1b9 100644
--- a/src/components/Office/TXOTabNav/TXOTabNav.jsx
+++ b/src/components/Office/TXOTabNav/TXOTabNav.jsx
@@ -17,6 +17,7 @@ const TXOTabNav = ({
excessWeightRiskCount,
pendingPaymentRequestCount,
unapprovedSITExtensionCount,
+ missingOrdersInfoCount,
shipmentsWithDeliveryAddressUpdateRequestedCount,
order,
moveCode,
@@ -39,6 +40,9 @@ const TXOTabNav = ({
if (shipmentsWithDeliveryAddressUpdateRequestedCount) {
moveDetailsTagCount += shipmentsWithDeliveryAddressUpdateRequestedCount;
}
+ if (missingOrdersInfoCount > 0) {
+ moveDetailsTagCount += missingOrdersInfoCount;
+ }
let moveTaskOrderTagCount = 0;
if (unapprovedServiceItemCount > 0) {
diff --git a/src/components/Office/TXOTabNav/TXOTabNav.test.jsx b/src/components/Office/TXOTabNav/TXOTabNav.test.jsx
index 7298a9d4d08..2779c53b11e 100644
--- a/src/components/Office/TXOTabNav/TXOTabNav.test.jsx
+++ b/src/components/Office/TXOTabNav/TXOTabNav.test.jsx
@@ -41,11 +41,12 @@ describe('Move details tag rendering', () => {
const moveDetailsOneShipment = {
...basicNavProps,
unapprovedShipmentCount: 1,
+ missingOrdersInfoCount: 4,
};
render(, { wrapper: MemoryRouter });
const moveDetailsTab = screen.getByTestId('MoveDetails-Tab');
- expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('1');
+ expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('5');
});
it('should render the move details tab container with a tag that shows the count of items that need attention when there are approved shipments with a destination address update requiring TXO review', () => {
diff --git a/src/components/PrimeUI/Shipment/Shipment.jsx b/src/components/PrimeUI/Shipment/Shipment.jsx
index 1625cc91240..ba59e51a98e 100644
--- a/src/components/PrimeUI/Shipment/Shipment.jsx
+++ b/src/components/PrimeUI/Shipment/Shipment.jsx
@@ -190,11 +190,31 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => {
{formatPrimeAPIShipmentAddress(shipment.pickupAddress)}
{shipment.pickupAddress?.id && moveId && Edit}
+
+
Second Pickup Address:
+ {formatPrimeAPIShipmentAddress(shipment.secondaryPickupAddress)}
+ {shipment.secondaryPickupAddress?.id && moveId && Edit}
+
+
+
Third Pickup Address:
+ {formatPrimeAPIShipmentAddress(shipment.tertiaryPickupAddress)}
+ {shipment.tertiaryPickupAddress?.id && moveId && Edit}
+
Destination Address:
{formatPrimeAPIShipmentAddress(shipment.destinationAddress)}
{shipment.destinationAddress?.id && moveId && Edit}
+
+
Second Destination Address:
+ {formatPrimeAPIShipmentAddress(shipment.secondaryDeliveryAddress)}
+ {shipment.secondaryDeliveryAddress?.id && moveId && Edit}
+
+
+
Third Destination Address:
+ {formatPrimeAPIShipmentAddress(shipment.tertiaryDeliveryAddress)}
+ {shipment.tertiaryDeliveryAddress?.id && moveId && Edit}
+
Destination type:
@@ -271,17 +291,25 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => {
{formatPrimeAPIShipmentAddress(shipment.ppmShipment.pickupAddress)}
-
Secondary Pickup Address:
+ Second Pickup Address:
{formatPrimeAPIShipmentAddress(shipment.ppmShipment.secondaryPickupAddress)}
+
+
Third Pickup Address:
+ {formatPrimeAPIShipmentAddress(shipment.ppmShipment.tertiaryPickupAddress)}
+
Destination Address:
{formatPrimeAPIShipmentAddress(shipment.ppmShipment.destinationAddress)}
-
Secondary Destination Address:
+ Second Destination Address:
{formatPrimeAPIShipmentAddress(shipment.ppmShipment.secondaryDestinationAddress)}
+
+
Third Destination Address:
+ {formatPrimeAPIShipmentAddress(shipment.ppmShipment.tertiaryDestinationAddress)}
+
PPM SIT Expected:
diff --git a/src/components/PrimeUI/Shipment/Shipment.test.jsx b/src/components/PrimeUI/Shipment/Shipment.test.jsx
index a1f528a46eb..85edaf6d209 100644
--- a/src/components/PrimeUI/Shipment/Shipment.test.jsx
+++ b/src/components/PrimeUI/Shipment/Shipment.test.jsx
@@ -60,13 +60,43 @@ const approvedMoveTaskOrder = {
requiredDeliveryDate: null,
scheduledPickupDate: '2020-03-16',
secondaryDeliveryAddress: {
- city: null,
- postalCode: null,
- state: null,
- streetAddress1: null,
+ city: 'Fairfield',
+ id: 'bfe61147-5fd7-426e-b473-54ccf77bde35',
+ postalCode: '94535',
+ state: 'CA',
+ streetAddress1: '986 Any Avenue',
+ streetAddress2: 'P.O. Box 9876',
+ streetAddress3: 'c/o Some Person',
+ },
+ secondaryPickupAddress: {
+ city: 'Beverly Hills',
+ id: 'cf159eca-162c-4131-84a0-795e684416a6',
+ postalCode: '90210',
+ state: 'CA',
+ streetAddress1: '124 Any Street',
+ streetAddress2: 'P.O. Box 12345',
+ streetAddress3: 'c/o Some Person',
},
shipmentType: 'HHG',
status: 'APPROVED',
+ tertiaryDeliveryAddress: {
+ city: 'Fairfield',
+ id: 'bfe61147-5fd7-426e-b473-54ccf77bde35',
+ postalCode: '94535',
+ state: 'CA',
+ streetAddress1: '985 Any Avenue',
+ streetAddress2: 'P.O. Box 9876',
+ streetAddress3: 'c/o Some Person',
+ },
+ tertiaryPickupAddress: {
+ city: 'Beverly Hills',
+ id: 'cf159eca-162c-4131-84a0-795e684416a6',
+ postalCode: '90210',
+ state: 'CA',
+ streetAddress1: '125 Any Street',
+ streetAddress2: 'P.O. Box 12345',
+ streetAddress3: 'c/o Some Person',
+ },
updatedAt: '2021-10-22',
mtoServiceItems: null,
reweigh: {
@@ -100,7 +130,7 @@ describe('Shipment details component', () => {
expect(addServiceItemLink).toBeInTheDocument();
expect(addServiceItemLink.getAttribute('href')).toBe(`/shipments/${shipmentId}/service-items/new`);
- expect(screen.queryAllByRole('link', { name: 'Edit' })).toHaveLength(3);
+ expect(screen.queryAllByRole('link', { name: 'Edit' })).toHaveLength(7);
});
it('renders the shipment address values', async () => {
@@ -108,7 +138,11 @@ describe('Shipment details component', () => {
const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0];
expect(screen.getByText(formatPrimeAPIFullAddress(shipment.pickupAddress))).toBeInTheDocument();
+ expect(screen.getByText(formatPrimeAPIFullAddress(shipment.secondaryPickupAddress))).toBeInTheDocument();
+ expect(screen.getByText(formatPrimeAPIFullAddress(shipment.tertiaryPickupAddress))).toBeInTheDocument();
expect(screen.getByText(formatPrimeAPIFullAddress(shipment.destinationAddress))).toBeInTheDocument();
+ expect(screen.getByText(formatPrimeAPIFullAddress(shipment.secondaryDeliveryAddress))).toBeInTheDocument();
+ expect(screen.getByText(formatPrimeAPIFullAddress(shipment.tertiaryDeliveryAddress))).toBeInTheDocument();
});
it('renders the shipment info', () => {
diff --git a/src/components/Table/TableCSVExportButton.jsx b/src/components/Table/TableCSVExportButton.jsx
index 813f5b7a50d..5d3180ede70 100644
--- a/src/components/Table/TableCSVExportButton.jsx
+++ b/src/components/Table/TableCSVExportButton.jsx
@@ -1,10 +1,12 @@
-import React, { useState, useRef } from 'react';
+import React, { useState, useRef, useContext } from 'react';
import { CSVLink } from 'react-csv';
-import { Link } from '@trussworks/react-uswds';
+import { Button } from '@trussworks/react-uswds';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import moment from 'moment';
import PropTypes from 'prop-types';
+import SelectedGblocContext from 'components/Office/GblocSwitcher/SelectedGblocContext';
+
const TableCSVExportButton = ({
labelText,
filePrefix,
@@ -16,12 +18,16 @@ const TableCSVExportButton = ({
paramSort,
paramFilters,
className,
+ isHeadquartersUser,
}) => {
const [isLoading, setIsLoading] = useState(false);
const [csvRows, setCsvRows] = useState([]);
const csvLinkRef = useRef(null);
const { id: sortColumn, desc: sortOrder } = paramSort.length ? paramSort[0] : {};
+ const gblocContext = useContext(SelectedGblocContext);
+ const { selectedGbloc } = isHeadquartersUser && gblocContext ? gblocContext : { selectedGbloc: undefined };
+
const formatDataForExport = (data, columns = tableColumns) => {
const formattedData = [];
data.forEach((row) => {
@@ -50,6 +56,7 @@ const TableCSVExportButton = ({
order: sortOrder ? 'desc' : 'asc',
filters: paramFilters,
currentPageSize: totalCount,
+ viewAsGBLOC: selectedGbloc,
});
const formattedData = formatDataForExport(response[queueFetcherKey]);
@@ -61,15 +68,23 @@ const TableCSVExportButton = ({
return (
-
+
@@ -96,6 +111,8 @@ TableCSVExportButton.propTypes = {
paramSort: PropTypes.array,
// paramSort is the filter columns and values currently applied to the queue
paramFilters: PropTypes.array,
+ // isHeadquartersUser identifies if the active role is a headquarters user to allow switching GBLOCs
+ isHeadquartersUser: PropTypes.bool,
};
TableCSVExportButton.defaultProps = {
@@ -104,6 +121,7 @@ TableCSVExportButton.defaultProps = {
hiddenColumns: [],
paramSort: [],
paramFilters: [],
+ isHeadquartersUser: false,
};
export default TableCSVExportButton;
diff --git a/src/components/Table/TableCSVExportButton.test.jsx b/src/components/Table/TableCSVExportButton.test.jsx
index a3847752000..da8b2102f13 100644
--- a/src/components/Table/TableCSVExportButton.test.jsx
+++ b/src/components/Table/TableCSVExportButton.test.jsx
@@ -55,6 +55,11 @@ const paymentRequestsResponse = {
],
};
+const paymentRequestsNoResultsResponse = {
+ page: 1,
+ perPage: 10,
+};
+
const paymentRequestColumns = [
{
Header: ' ',
@@ -129,6 +134,9 @@ const paymentRequestColumns = [
jest.mock('services/ghcApi', () => ({
getPaymentRequestsQueue: jest.fn().mockImplementation(() => Promise.resolve(paymentRequestsResponse)),
+ getPaymentRequestsNoResultsQueue: jest
+ .fn()
+ .mockImplementation(() => Promise.resolve(paymentRequestsNoResultsResponse)),
}));
describe('TableCSVExportButton', () => {
@@ -155,4 +163,22 @@ describe('TableCSVExportButton', () => {
expect(getPaymentRequestsQueue).toBeCalled();
});
+
+ const noResultsProps = {
+ tableColumns: paymentRequestColumns,
+ queueFetcher: () => Promise.resolve(paymentRequestsNoResultsResponse),
+ queueFetcherKey: 'queuePaymentRequests',
+ totalCount: 0,
+ };
+
+ it('is diabled when there is nothing to export', () => {
+ act(() => {
+ const wrapper = mount();
+ const exportButton = wrapper.find('span[data-test-id="csv-export-btn-text"]');
+ exportButton.simulate('click');
+ wrapper.update();
+ });
+
+ expect(getPaymentRequestsQueue).toBeCalled();
+ });
});
diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx
index 5182fed428d..d09605914c4 100644
--- a/src/components/Table/TableQueue.jsx
+++ b/src/components/Table/TableQueue.jsx
@@ -48,6 +48,7 @@ const TableQueue = ({
csvExportQueueFetcher,
csvExportQueueFetcherKey,
sessionStorageKey,
+ isHeadquartersUser,
}) => {
const [isPageReload, setIsPageReload] = useState(true);
useEffect(() => {
@@ -87,7 +88,7 @@ const TableQueue = ({
const { id, desc } = paramSort.length ? paramSort[0] : {};
const gblocContext = useContext(SelectedGblocContext);
- const { selectedGbloc } = gblocContext || { selectedGbloc: undefined };
+ const { selectedGbloc } = isHeadquartersUser && gblocContext ? gblocContext : { selectedGbloc: undefined };
const multiSelectValueDelimiter = ',';
@@ -312,6 +313,7 @@ const TableQueue = ({
totalCount={totalCount}
paramSort={paramSort}
paramFilters={paramFilters}
+ isHeadquartersUser={isHeadquartersUser}
/>
)}
@@ -381,6 +383,8 @@ TableQueue.propTypes = {
csvExportQueueFetcherKey: PropTypes.string,
// session storage key to store search filters
sessionStorageKey: PropTypes.string,
+ // isHeadquartersUser identifies if the active role is a headquarters user to allow switching GBLOCs
+ isHeadquartersUser: PropTypes.bool,
};
TableQueue.defaultProps = {
@@ -399,5 +403,6 @@ TableQueue.defaultProps = {
csvExportQueueFetcher: null,
csvExportQueueFetcherKey: null,
sessionStorageKey: 'default',
+ isHeadquartersUser: false,
};
export default TableQueue;
diff --git a/src/pages/MyMove/Profile/ContactInfo.jsx b/src/pages/MyMove/Profile/ContactInfo.jsx
index 2b25f520797..298789034a0 100644
--- a/src/pages/MyMove/Profile/ContactInfo.jsx
+++ b/src/pages/MyMove/Profile/ContactInfo.jsx
@@ -37,13 +37,13 @@ export const ContactInfo = ({ serviceMember, updateServiceMember, userEmail }) =
const payload = {
id: serviceMember.id,
telephone: values?.telephone,
- secondary_telephone: values?.secondary_telephone,
+ secondary_telephone: values?.secondary_telephone || '',
personal_email: values?.personal_email,
phone_is_preferred: values?.phone_is_preferred,
email_is_preferred: values?.email_is_preferred,
};
- if (!payload.secondary_telephone) {
- delete payload.secondary_telephone;
+ if (!payload.secondary_telephone || payload.secondary_telephone === '') {
+ payload.secondary_telephone = '';
}
return patchServiceMember(payload)
diff --git a/src/pages/MyMove/Profile/EditContactInfo.jsx b/src/pages/MyMove/Profile/EditContactInfo.jsx
index aa27b30eae0..898031e46b8 100644
--- a/src/pages/MyMove/Profile/EditContactInfo.jsx
+++ b/src/pages/MyMove/Profile/EditContactInfo.jsx
@@ -73,9 +73,7 @@ export const EditContactInfo = ({
backup_mailing_address: values[backupAddressName.toString()],
};
- if (values?.secondary_telephone) {
- serviceMemberPayload.secondary_telephone = values?.secondary_telephone;
- }
+ serviceMemberPayload.secondary_telephone = values?.secondary_telephone;
const backupContactPayload = {
id: currentBackupContacts[0].id,
diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx
index b8a6ed6a6fa..72f27a84ca5 100644
--- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx
+++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx
@@ -27,11 +27,14 @@ import { setFlashMessage as setFlashMessageAction } from 'store/flash/actions';
import { elevatedPrivilegeTypes } from 'constants/userPrivileges';
import { isBooleanFlagEnabled } from 'utils/featureFlags';
import departmentIndicators from 'constants/departmentIndicators';
+import { generateUniqueDodid, generateUniqueEmplid } from 'utils/customer';
+import Hint from 'components/Hint';
export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {
const [serverError, setServerError] = useState(null);
const [showEmplid, setShowEmplid] = useState(false);
const [isSafetyMove, setIsSafetyMove] = useState(false);
+ const [showSafetyMoveHint, setShowSafetyMoveHint] = useState(false);
const navigate = useNavigate();
const branchOptions = dropdownInputOptions(SERVICE_MEMBER_AGENCY_LABELS);
@@ -42,6 +45,9 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {
const [isSafetyMoveFF, setSafetyMoveFF] = useState(false);
+ const uniqueDodid = generateUniqueDodid();
+ const uniqueEmplid = generateUniqueEmplid();
+
useEffect(() => {
isBooleanFlagEnabled('safety_move')?.then((enabled) => {
setSafetyMoveFF(enabled);
@@ -55,6 +61,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {
const initialValues = {
affiliation: '',
edipi: '',
+ emplid: '',
first_name: '',
middle_name: '',
last_name: '',
@@ -87,7 +94,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {
},
create_okta_account: '',
cac_user: '',
- is_safety_move: false,
+ is_safety_move: 'false',
};
const handleBack = () => {
@@ -144,10 +151,8 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {
const validationSchema = Yup.object().shape({
affiliation: Yup.mixed().oneOf(Object.keys(SERVICE_MEMBER_AGENCY_LABELS)).required('Required'),
- edipi: Yup.string().matches(/[0-9]{10}/, 'Enter a 10-digit DOD ID number'),
- emplid: Yup.string()
- .notRequired()
- .matches(/[0-9]{7}/, 'Enter a 7-digit EMPLID number'),
+ edipi: Yup.string().matches(/^(SM[0-9]{8}|[0-9]{10})$/, 'Enter a 10-digit DOD ID number'),
+ emplid: Yup.string().matches(/^(SM[0-9]{5}|[0-9]{7})$/, 'Enter a 7-digit EMPLID number'),
first_name: Yup.string().required('Required'),
middle_name: Yup.string(),
last_name: Yup.string().required('Required'),
@@ -193,11 +198,10 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {
const { value } = e.target;
if (value === 'true') {
setIsSafetyMove(true);
- // clear out DoDID, emplid, and OKTA fields
+ setShowSafetyMoveHint(true);
setValues({
...values,
- edipi: '',
- emplid: '',
+ affiliation: '',
create_okta_account: '',
cac_user: 'true',
is_safety_move: 'true',
@@ -206,15 +210,47 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {
setIsSafetyMove(false);
setValues({
...values,
+ affiliation: '',
+ edipi: '',
+ emplid: '',
is_safety_move: 'false',
});
}
};
const handleBranchChange = (e) => {
- if (e.target.value === departmentIndicators.COAST_GUARD) {
+ setShowSafetyMoveHint(false);
+ if (e.target.value === departmentIndicators.COAST_GUARD && isSafetyMove) {
setShowEmplid(true);
+ setValues({
+ ...values,
+ affiliation: e.target.value,
+ edipi: uniqueDodid,
+ emplid: uniqueEmplid,
+ });
+ } else if (e.target.value === departmentIndicators.COAST_GUARD && !isSafetyMove) {
+ setShowEmplid(true);
+ setValues({
+ ...values,
+ affiliation: e.target.value,
+ edipi: '',
+ emplid: '',
+ });
+ } else if (e.target.value !== departmentIndicators.COAST_GUARD && isSafetyMove) {
+ setShowEmplid(false);
+ setValues({
+ ...values,
+ affiliation: e.target.value,
+ edipi: uniqueDodid,
+ emplid: '',
+ });
} else {
setShowEmplid(false);
+ setValues({
+ ...values,
+ affiliation: e.target.value,
+ edipi: '',
+ emplid: '',
+ });
}
};
return (
@@ -234,6 +270,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {
value="true"
data-testid="is-safety-move-yes"
onChange={handleIsSafetyMove}
+ checked={values.is_safety_move === 'true'}
/>
{
value="false"
data-testid="is-safety-move-no"
onChange={handleIsSafetyMove}
+ checked={values.is_safety_move === 'false'}
/>
@@ -262,9 +300,9 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => {
label="DoD ID number"
name="edipi"
id="edipi"
- labelHint="Optional"
maxLength="10"
isDisabled={isSafetyMove}
+ data-testid="edipiInput"
/>
{showEmplid && (
{
name="emplid"
id="emplid"
maxLength="7"
- labelHint="Optional"
inputMode="numeric"
pattern="[0-9]{7}"
isDisabled={isSafetyMove}
+ data-testid="emplidInput"
/>
)}
+ {isSafetyMove && showSafetyMoveHint && (
+
+ Once a branch is selected, this will generate a random safety move identifier
+
+ )}
Customer Name
diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx
index 7167c40da84..088b20f415e 100644
--- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx
+++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx
@@ -69,7 +69,7 @@ const fakePayload = {
},
create_okta_account: 'true',
cac_user: 'false',
- is_safety_move: 'false',
+ is_safety_move: false,
};
const fakeResponse = {
@@ -215,6 +215,66 @@ describe('CreateCustomerForm', () => {
expect(screen.getByText('EMPLID')).toBeInTheDocument();
});
+ it('payload can have an empty secondary phone number', async () => {
+ createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse));
+
+ const { getByLabelText, getByTestId, getByRole } = render(
+
+
+ ,
+ );
+
+ const user = userEvent.setup();
+
+ const saveBtn = await screen.findByRole('button', { name: 'Save' });
+ expect(saveBtn).toBeInTheDocument();
+
+ await user.selectOptions(getByLabelText('Branch of service'), [fakePayload.affiliation]);
+
+ await user.type(getByLabelText('First name'), fakePayload.first_name);
+ await user.type(getByLabelText('Last name'), fakePayload.last_name);
+
+ await user.type(getByLabelText('Best contact phone'), fakePayload.telephone);
+ await user.type(getByLabelText('Personal email'), fakePayload.personal_email);
+
+ await user.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1);
+ await user.type(getByTestId('res-add-city'), fakePayload.residential_address.city);
+ await user.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]);
+ await user.type(getByTestId('res-add-zip'), fakePayload.residential_address.postalCode);
+
+ await user.type(getByTestId('backup-add-street1'), fakePayload.backup_mailing_address.streetAddress1);
+ await user.type(getByTestId('backup-add-city'), fakePayload.backup_mailing_address.city);
+ await user.selectOptions(getByTestId('backup-add-state'), [fakePayload.backup_mailing_address.state]);
+ await user.type(getByTestId('backup-add-zip'), fakePayload.backup_mailing_address.postalCode);
+
+ await user.type(getByLabelText('Name'), fakePayload.backup_contact.name);
+ await user.type(getByRole('textbox', { name: 'Email' }), fakePayload.backup_contact.email);
+ await user.type(getByRole('textbox', { name: 'Phone' }), fakePayload.backup_contact.telephone);
+
+ await userEvent.type(getByTestId('create-okta-account-yes'), fakePayload.create_okta_account);
+
+ await userEvent.type(getByTestId('cac-user-no'), fakePayload.cac_user);
+
+ await waitFor(() => {
+ expect(saveBtn).toBeEnabled();
+ });
+
+ const waiter = waitFor(() => {
+ expect(createCustomerWithOktaOption).toHaveBeenCalled();
+ expect(mockNavigate).toHaveBeenCalledWith(ordersPath, {
+ state: {
+ isSafetyMoveSelected: false,
+ },
+ });
+ });
+
+ await user.click(saveBtn);
+ await waiter;
+ expect(mockNavigate).toHaveBeenCalled();
+
+ expect(createCustomerWithOktaOption.mock.calls[0][0]).not.toHaveProperty('secondary_number');
+ }, 10000);
+
it('navigates the user on cancel click', async () => {
const { getByText } = render(
@@ -335,6 +395,71 @@ describe('CreateCustomerForm', () => {
});
}, 10000);
+ it('disables and populates DODID and EMPLID inputs when safety move is selected', async () => {
+ createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse));
+ isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true));
+
+ const { getByLabelText, getByTestId, getByRole } = render(
+
+
+ ,
+ );
+
+ const user = userEvent.setup();
+
+ const safetyMove = await screen.findByTestId('is-safety-move-no');
+ expect(safetyMove).toBeChecked();
+
+ // check the safety move box
+ await userEvent.type(getByTestId('is-safety-move-yes'), safetyPayload.is_safety_move);
+
+ expect(await screen.findByTestId('safetyMoveHint')).toBeInTheDocument();
+
+ await user.selectOptions(getByLabelText('Branch of service'), ['COAST_GUARD']);
+
+ // the input boxes should now be disabled
+ expect(await screen.findByTestId('edipiInput')).toBeDisabled();
+ expect(await screen.findByTestId('emplidInput')).toBeDisabled();
+
+ // should be able to submit the form
+ await user.type(getByLabelText('First name'), safetyPayload.first_name);
+ await user.type(getByLabelText('Last name'), safetyPayload.last_name);
+
+ await user.type(getByLabelText('Best contact phone'), safetyPayload.telephone);
+ await user.type(getByLabelText('Personal email'), safetyPayload.personal_email);
+
+ await userEvent.type(getByTestId('res-add-street1'), safetyPayload.residential_address.streetAddress1);
+ await userEvent.type(getByTestId('res-add-city'), safetyPayload.residential_address.city);
+ await userEvent.selectOptions(getByTestId('res-add-state'), [safetyPayload.residential_address.state]);
+ await userEvent.type(getByTestId('res-add-zip'), safetyPayload.residential_address.postalCode);
+
+ await userEvent.type(getByTestId('backup-add-street1'), safetyPayload.backup_mailing_address.streetAddress1);
+ await userEvent.type(getByTestId('backup-add-city'), safetyPayload.backup_mailing_address.city);
+ await userEvent.selectOptions(getByTestId('backup-add-state'), [safetyPayload.backup_mailing_address.state]);
+ await userEvent.type(getByTestId('backup-add-zip'), safetyPayload.backup_mailing_address.postalCode);
+
+ await userEvent.type(getByLabelText('Name'), safetyPayload.backup_contact.name);
+ await userEvent.type(getByRole('textbox', { name: 'Email' }), safetyPayload.backup_contact.email);
+ await userEvent.type(getByRole('textbox', { name: 'Phone' }), safetyPayload.backup_contact.telephone);
+
+ const saveBtn = await screen.findByRole('button', { name: 'Save' });
+ expect(saveBtn).toBeInTheDocument();
+
+ await waitFor(() => {
+ expect(saveBtn).toBeEnabled();
+ });
+ await userEvent.click(saveBtn);
+
+ await waitFor(() => {
+ expect(createCustomerWithOktaOption).toHaveBeenCalled();
+ expect(mockNavigate).toHaveBeenCalledWith(ordersPath, {
+ state: {
+ isSafetyMoveSelected: true,
+ },
+ });
+ });
+ }, 10000);
+
it('submits the form and tests for unsupported state validation', async () => {
createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse));
diff --git a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx
index 9c6f613cc7e..8ba085fc464 100644
--- a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx
+++ b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx
@@ -247,6 +247,7 @@ const HeadquartersQueue = () => {
csvExportQueueFetcher={getMovesQueue}
csvExportQueueFetcherKey="queueMoves"
sessionStorageKey={queueType}
+ isHeadquartersUser
/>
);
@@ -273,6 +274,7 @@ const HeadquartersQueue = () => {
csvExportQueueFetcher={getPaymentRequestsQueue}
csvExportQueueFetcherKey="queuePaymentRequests"
sessionStorageKey={queueType}
+ isHeadquartersUser
/>
);
@@ -299,6 +301,7 @@ const HeadquartersQueue = () => {
csvExportQueueFetcher={getServicesCounselingPPMQueue}
csvExportQueueFetcherKey="queueMoves"
sessionStorageKey={queueType}
+ isHeadquartersUser
/>
);
@@ -326,6 +329,7 @@ const HeadquartersQueue = () => {
csvExportQueueFetcher={getServicesCounselingQueue}
csvExportQueueFetcherKey="queueMoves"
sessionStorageKey={queueType}
+ isHeadquartersUser
/>
);
diff --git a/src/pages/Office/MoveDetails/MoveDetails.jsx b/src/pages/Office/MoveDetails/MoveDetails.jsx
index 8d6a4c359ac..e2fb69d7450 100644
--- a/src/pages/Office/MoveDetails/MoveDetails.jsx
+++ b/src/pages/Office/MoveDetails/MoveDetails.jsx
@@ -57,6 +57,8 @@ const MoveDetails = ({
setExcessWeightRiskCount,
setUnapprovedSITExtensionCount,
setShipmentsWithDeliveryAddressUpdateRequestedCount,
+ missingOrdersInfoCount,
+ setMissingOrdersInfoCount,
isMoveLocked,
}) => {
const { moveCode } = useParams();
@@ -238,6 +240,28 @@ const MoveDetails = ({
setShipmentMissingRequiredInformation(shipmentIsMissingInformation);
}, [mtoShipments]);
+ // using useMemo here due to this being used in a useEffect
+ // using useMemo prevents the useEffect from being rendered on ever render by memoizing the object
+ // so that it only recognizes the change when the orders object changes
+ const requiredOrdersInfo = useMemo(
+ () => ({
+ ordersNumber: order?.order_number || '',
+ ordersType: order?.order_type || '',
+ ordersTypeDetail: order?.order_type_detail || '',
+ tacMDC: order?.tac || '',
+ departmentIndicator: order?.department_indicator || '',
+ }),
+ [order],
+ );
+
+ // Keep num of missing orders info synced up
+ useEffect(() => {
+ const ordersInfoCount = Object.values(requiredOrdersInfo).reduce((count, value) => {
+ return !value ? count + 1 : count;
+ }, 0);
+ setMissingOrdersInfoCount(ordersInfoCount);
+ }, [order, requiredOrdersInfo, setMissingOrdersInfoCount]);
+
if (isLoading) return ;
if (isError) return ;
@@ -284,7 +308,7 @@ const MoveDetails = ({
const customerInfo = {
name: formattedCustomerName(customer.last_name, customer.first_name, customer.suffix, customer.middle_name),
agency: customer.agency,
- dodId: customer.dodID,
+ edipi: customer.edipi,
emplid: customer.emplid,
phone: customer.phone,
altPhone: customer.secondaryTelephone,
@@ -294,13 +318,6 @@ const MoveDetails = ({
backupContact: customer.backup_contact,
};
- const requiredOrdersInfo = {
- ordersNumber: order.order_number,
- ordersType: order.order_type,
- ordersTypeDetail: order.order_type_detail,
- tacMDC: order.tac,
- };
-
const hasMissingOrdersRequiredInfo = Object.values(requiredOrdersInfo).some((value) => !value || value === '');
const hasAmendedOrders = ordersInfo.uploadedAmendedOrderID && !ordersInfo.amendedOrdersAcknowledgedAt;
const hasDestinationAddressUpdate =
@@ -311,12 +328,12 @@ const MoveDetails = ({
-
+ {missingOrdersInfoCount}
({
...jest.requireActual('react-router-dom'),
@@ -360,6 +361,7 @@ const requestedMoveDetailsAmendedOrdersQuery = {
},
order: {
id: '1',
+ department_indicator: 'ARMY',
originDutyLocation: {
address: {
streetAddress1: '',
@@ -783,6 +785,8 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount}
+ missingOrdersInfoCount={0}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
@@ -801,6 +805,8 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount}
+ missingOrdersInfoCount={0}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
@@ -819,6 +825,8 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount}
+ missingOrdersInfoCount={0}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
@@ -885,6 +893,8 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount}
+ missingOrdersInfoCount={0}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
@@ -905,6 +915,8 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount}
+ missingOrdersInfoCount={0}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
@@ -928,6 +940,8 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount}
+ missingOrdersInfoCount={0}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
@@ -967,6 +981,8 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedServiceItemCount}
+ missingOrdersInfoCount={0}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
@@ -986,12 +1002,15 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount}
+ missingOrdersInfoCount={2}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
it('renders an error indicator in the sidebar', () => {
expect(wrapper.find('a[href="#orders"] span[data-testid="tag"]').exists()).toBe(true);
+ expect(wrapper.find('a[href="#orders"] span[data-testid="tag"]').text()).toBe('2');
});
});
@@ -1006,6 +1025,8 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount}
+ missingOrdersInfoCount={0}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
@@ -1025,6 +1046,8 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount}
+ missingOrdersInfoCount={0}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
@@ -1039,6 +1062,7 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount,
setExcessWeightRiskCount,
setUnapprovedSITExtensionCount,
+ setMissingOrdersInfoCount,
};
it('renders the financial review flag button when user has permission', async () => {
@@ -1150,6 +1174,8 @@ describe('MoveDetails page', () => {
setUnapprovedServiceItemCount={setUnapprovedServiceItemCount}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount}
+ missingOrdersInfoCount={0}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
/>
,
);
diff --git a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx
index 6c065a37083..104aac73b43 100644
--- a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx
+++ b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx
@@ -1,7 +1,6 @@
import React, { useState, useEffect, useMemo } from 'react';
import { Link, useParams, useNavigate, generatePath } from 'react-router-dom';
import { useQueryClient, useMutation } from '@tanstack/react-query';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { func } from 'prop-types';
import classnames from 'classnames';
import 'styles/office.scss';
@@ -42,7 +41,13 @@ import { objectIsMissingFieldWithCondition } from 'utils/displayFlags';
import { ReviewButton } from 'components/form/IconButtons';
import { calculateWeightRequested } from 'hooks/custom';
-const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCount, isMoveLocked }) => {
+const ServicesCounselingMoveDetails = ({
+ infoSavedAlert,
+ setUnapprovedShipmentCount,
+ isMoveLocked,
+ missingOrdersInfoCount,
+ setMissingOrdersInfoCount,
+}) => {
const { moveCode } = useParams();
const navigate = useNavigate();
const [alertMessage, setAlertMessage] = useState(null);
@@ -345,6 +350,20 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo
ntsSac: order.ntsSac,
};
+ // using useMemo here due to this being used in a useEffect
+ // using useMemo prevents the useEffect from being rendered on ever render by memoizing the object
+ // so that it only recognizes the change when the orders object changes
+ const requiredOrdersInfo = useMemo(
+ () => ({
+ ordersNumber: order?.order_number || '',
+ ordersType: order?.order_type || '',
+ ordersTypeDetail: order?.order_type_detail || '',
+ tacMDC: order?.tac || '',
+ departmentIndicator: order?.department_indicator || '',
+ }),
+ [order],
+ );
+
const handleButtonDropdownChange = (e) => {
const selectedOption = e.target.value;
@@ -412,6 +431,14 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo
setUnapprovedShipmentCount,
]);
+ // Keep num of missing orders info synced up
+ useEffect(() => {
+ const ordersInfoCount = Object.values(requiredOrdersInfo).reduce((count, value) => {
+ return !value ? count + 1 : count;
+ }, 0);
+ setMissingOrdersInfoCount(ordersInfoCount);
+ }, [order, requiredOrdersInfo, setMissingOrdersInfoCount]);
+
if (isLoading) return ;
if (isError) return ;
@@ -454,13 +481,6 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo
return false;
};
- const requiredOrdersInfo = {
- ordersNumber: order.order_number,
- ordersType: order.order_type,
- ordersTypeDetail: order.order_type_detail,
- tacMDC: order.tac,
- };
-
const allShipmentsDeleted = mtoShipments.every((shipment) => !!shipment.deletedAt);
const hasMissingOrdersRequiredInfo = Object.values(requiredOrdersInfo).some((value) => !value || value === '');
const hasAmendedOrders = ordersInfo.uploadedAmendedOrderID && !ordersInfo.amendedOrdersAcknowledgedAt;
@@ -477,12 +497,12 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo
{shipmentConcernCount}
-
+ {missingOrdersInfoCount}
{
return render(
-
+
,
);
};
@@ -570,6 +638,23 @@ describe('MoveDetails page', () => {
expect(mockSetUpapprovedShipmentCount).toHaveBeenCalledWith(3);
});
+ it('shares the number of missing orders information', () => {
+ const moveDetailsQuery = {
+ ...newMoveDetailsQuery,
+ order: orderMissingRequiredInfo,
+ };
+
+ useMoveDetailsQueries.mockReturnValue(moveDetailsQuery);
+ useOrdersDocumentQueries.mockReturnValue(moveDetailsQuery);
+
+ const mockSetMissingOrdersInfoCount = jest.fn();
+ renderComponent({ setMissingOrdersInfoCount: mockSetMissingOrdersInfoCount });
+
+ // Should have called `setMissingOrdersInfoCount` with 4 missing fields
+ expect(mockSetMissingOrdersInfoCount).toHaveBeenCalledTimes(1);
+ expect(mockSetMissingOrdersInfoCount).toHaveBeenCalledWith(4);
+ });
+
/* eslint-disable camelcase */
it('renders shipments info', async () => {
useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery);
@@ -836,7 +921,11 @@ describe('MoveDetails page', () => {
permissions={[permissionTypes.updateShipment, permissionTypes.updateCustomer]}
{...mockRoutingOptions}
>
-
+
,
);
@@ -933,7 +1022,11 @@ describe('MoveDetails page', () => {
path={servicesCounselingRoutes.BASE_SHIPMENT_ADD_PATH}
params={{ moveCode: mockRequestedMoveCode, shipmentType }}
>
- ,
+
+ ,
,
);
@@ -1031,7 +1124,10 @@ describe('MoveDetails page', () => {
it('renders the financial review flag button when user has permission', async () => {
render(
-
+
,
);
diff --git a/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx b/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx
index 8646b252640..eaedf6eca8a 100644
--- a/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx
+++ b/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx
@@ -42,6 +42,7 @@ const ServicesCounselingMoveInfo = () => {
const [unapprovedServiceItemCount, setUnapprovedServiceItemCount] = React.useState(0);
const [excessWeightRiskCount, setExcessWeightRiskCount] = React.useState(0);
const [unapprovedSITExtensionCount, setUnApprovedSITExtensionCount] = React.useState(0);
+ const [missingOrdersInfoCount, setMissingOrdersInfoCount] = useState(0);
const [infoSavedAlert, setInfoSavedAlert] = useState(null);
const { hasRecentError, traceId } = useSelector((state) => state.interceptor);
const [moveLockFlag, setMoveLockFlag] = useState(false);
@@ -195,6 +196,7 @@ const ServicesCounselingMoveInfo = () => {
unapprovedServiceItemCount={unapprovedServiceItemCount}
excessWeightRiskCount={excessWeightRiskCount}
unapprovedSITExtensionCount={unapprovedSITExtensionCount}
+ missingOrdersInfoCount={missingOrdersInfoCount}
/>
)}
@@ -214,6 +216,8 @@ const ServicesCounselingMoveInfo = () => {
}
diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx
index 902ac0aae5f..398bc9fe860 100644
--- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx
+++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx
@@ -184,6 +184,10 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor)
return row.originDutyLocation?.name;
},
}),
+ createHeader('Counseling office', 'counselingOffice', {
+ id: 'counselingOffice',
+ isFilterable: true,
+ }),
];
export const closeoutColumns = (moveLockFlag, ppmCloseoutGBLOC, ppmCloseoutOriginLocationList, supervisor) => [
createHeader(
diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx
index 7e41c292700..04f43e99710 100644
--- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx
+++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx
@@ -117,6 +117,7 @@ const needsCounselingMoves = {
name: 'Los Alamos',
},
originGBLOC: 'LKNQ',
+ counselingOffice: '',
},
{
id: 'move3',
@@ -178,6 +179,7 @@ const serviceCounselingCompletedMoves = {
name: 'Los Alamos',
},
originGBLOC: 'LKNQ',
+ counselingOffice: '67592323-fc7e-4b35-83a7-57faa53b7acf',
},
],
},
diff --git a/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx b/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx
index a931a110286..bbe6c9c4f09 100644
--- a/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx
+++ b/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx
@@ -39,6 +39,7 @@ const TXOMoveInfo = () => {
const [excessWeightRiskCount, setExcessWeightRiskCount] = React.useState(0);
const [pendingPaymentRequestCount, setPendingPaymentRequestCount] = React.useState(0);
const [unapprovedSITExtensionCount, setUnApprovedSITExtensionCount] = React.useState(0);
+ const [missingOrdersInfoCount, setMissingOrdersInfoCount] = useState(0);
const [moveLockFlag, setMoveLockFlag] = useState(false);
const [isMoveLocked, setIsMoveLocked] = useState(false);
@@ -150,6 +151,7 @@ const TXOMoveInfo = () => {
excessWeightRiskCount={excessWeightRiskCount}
pendingPaymentRequestCount={pendingPaymentRequestCount}
unapprovedSITExtensionCount={unapprovedSITExtensionCount}
+ missingOrdersInfoCount={missingOrdersInfoCount}
moveCode={moveCode}
reportId={reportId}
order={order}
@@ -177,6 +179,8 @@ const TXOMoveInfo = () => {
}
setExcessWeightRiskCount={setExcessWeightRiskCount}
setUnapprovedSITExtensionCount={setUnApprovedSITExtensionCount}
+ missingOrdersInfoCount={missingOrdersInfoCount}
+ setMissingOrdersInfoCount={setMissingOrdersInfoCount}
isMoveLocked={isMoveLocked}
/>
}
diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx
index e581487702c..699700e7ee8 100644
--- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx
+++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx
@@ -122,7 +122,11 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => {
const editableProGearWeightActualField = true;
const editableSpouseProGearWeightActualField = true;
const reformatPrimeApiPickupAddress = fromPrimeAPIAddressFormat(shipment.pickupAddress);
+ const reformatPrimeApiSecondaryPickupAddress = fromPrimeAPIAddressFormat(shipment.secondaryPickupAddress);
+ const reformatPrimeApiTertiaryPickupAddress = fromPrimeAPIAddressFormat(shipment.tertiaryPickupAddress);
const reformatPrimeApiDestinationAddress = fromPrimeAPIAddressFormat(shipment.destinationAddress);
+ const reformatPrimeApiSecondaryDeliveryAddress = fromPrimeAPIAddressFormat(shipment.secondaryDeliveryAddress);
+ const reformatPrimeApiTertiaryDeliveryAddress = fromPrimeAPIAddressFormat(shipment.tertiaryDeliveryAddress);
const editablePickupAddress = isEmpty(reformatPrimeApiPickupAddress);
const editableDestinationAddress = isEmpty(reformatPrimeApiDestinationAddress);
@@ -309,7 +313,11 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => {
scheduledDeliveryDate: shipment.scheduledDeliveryDate,
actualDeliveryDate: shipment.actualDeliveryDate,
pickupAddress: editablePickupAddress ? emptyAddress : reformatPrimeApiPickupAddress,
+ secondaryPickupAddress: reformatPrimeApiSecondaryPickupAddress,
+ tertiaryPickupAddress: reformatPrimeApiTertiaryPickupAddress,
destinationAddress: editableDestinationAddress ? emptyAddress : reformatPrimeApiDestinationAddress,
+ secondaryDeliveryAddress: reformatPrimeApiSecondaryDeliveryAddress,
+ tertiaryDeliveryAddress: reformatPrimeApiTertiaryDeliveryAddress,
destinationType: shipment.destinationType,
diversion: shipment.diversion,
};
@@ -364,7 +372,11 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => {
actualSpouseProGearWeight={initialValues.actualSpouseProGearWeight}
requestedPickupDate={initialValues.requestedPickupDate}
pickupAddress={initialValues.pickupAddress}
+ secondaryPickupAddress={initialValues.secondaryPickupAddress}
+ tertiaryPickupAddress={initialValues.tertiaryPickupAddress}
destinationAddress={initialValues.destinationAddress}
+ secondaryDeliveryAddress={initialValues.secondaryDeliveryAddress}
+ tertiaryDeliveryAddress={initialValues.tertiaryDeliveryAddress}
diversion={initialValues.diversion}
/>
)}
diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx
index 0c71eb62549..50d8bdb2a57 100644
--- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx
+++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx
@@ -35,7 +35,11 @@ const PrimeUIShipmentUpdateForm = ({
actualProGearWeight,
actualSpouseProGearWeight,
pickupAddress,
+ secondaryPickupAddress,
+ tertiaryPickupAddress,
destinationAddress,
+ secondaryDeliveryAddress,
+ tertiaryDeliveryAddress,
}) => {
return (
@@ -142,9 +146,17 @@ const PrimeUIShipmentUpdateForm = ({
Pickup Address
{editablePickupAddress && }
{!editablePickupAddress && formatAddress(pickupAddress)}
+ Second Pickup Address
+ {formatAddress(secondaryPickupAddress)}
+ Third Pickup Address
+ {formatAddress(tertiaryPickupAddress)}
Destination Address
{editableDestinationAddress && }
{!editableDestinationAddress && formatAddress(destinationAddress)}
+ Second Destination Address
+ {formatAddress(secondaryDeliveryAddress)}
+ Third Destination Address
+ {formatAddress(tertiaryDeliveryAddress)}
{
return generalRoutes.HOME_PATH;
}
};
+
+export const generateUniqueDodid = () => {
+ const prefix = 'SM';
+
+ // Custom epoch start date (e.g., 2024-01-01), generates something like 1704067200000
+ const customEpoch = new Date('2024-01-01').getTime();
+ const now = Date.now();
+
+ // Calculate milliseconds since custom epoch, then convert to an 8-digit integer
+ const uniqueNumber = Math.floor((now - customEpoch) / 1000); // Dividing by 1000 to reduce to seconds
+
+ // Convert the unique number to a string, ensuring it has 8 digits
+ const uniqueStr = uniqueNumber.toString().slice(0, 8).padStart(8, '0');
+
+ return prefix + uniqueStr;
+};
+
+export const generateUniqueEmplid = () => {
+ const prefix = 'SM';
+ const customEpoch = new Date('2024-01-01').getTime();
+ const now = Date.now();
+ const uniqueNumber = Math.floor((now - customEpoch) / 1000) % 100000; // Modulo 100000 ensures it's 5 digits
+ const uniqueStr = uniqueNumber.toString().padStart(5, '0');
+ return prefix + uniqueStr;
+};
diff --git a/swagger-def/definitions/ServiceItemParamName.yaml b/swagger-def/definitions/ServiceItemParamName.yaml
index 066dd32d45e..c3361653fbe 100644
--- a/swagger-def/definitions/ServiceItemParamName.yaml
+++ b/swagger-def/definitions/ServiceItemParamName.yaml
@@ -69,3 +69,4 @@ enum:
- StandaloneCrate
- StandaloneCrateCap
- UncappedRequestTotal
+ - LockedPriceCents
diff --git a/swagger-def/definitions/prime/v3/MTOShipmentWithoutServiceItems.yaml b/swagger-def/definitions/prime/v3/MTOShipmentWithoutServiceItems.yaml
index 4adad7ff907..dfd4710c6aa 100644
--- a/swagger-def/definitions/prime/v3/MTOShipmentWithoutServiceItems.yaml
+++ b/swagger-def/definitions/prime/v3/MTOShipmentWithoutServiceItems.yaml
@@ -166,13 +166,13 @@ properties:
destinationType:
$ref: '../../DestinationType.yaml'
secondaryPickupAddress:
- description: A second pickup address for this shipment, if the customer entered one. An optional field.
- allOf:
- - $ref: '../../Address.yaml'
+ $ref: '../../Address.yaml'
secondaryDeliveryAddress:
- description: A second delivery address for this shipment, if the customer entered one. An optional field.
- allOf:
- - $ref: '../../Address.yaml'
+ $ref: '../../Address.yaml'
+ tertiaryPickupAddress:
+ $ref: '../../Address.yaml'
+ tertiaryDeliveryAddress:
+ $ref: '../../Address.yaml'
storageFacility:
allOf:
- x-nullable: true
diff --git a/swagger-def/definitions/prime/v3/PPMShipment.yaml b/swagger-def/definitions/prime/v3/PPMShipment.yaml
index 278aeac423e..20d29db9f97 100644
--- a/swagger-def/definitions/prime/v3/PPMShipment.yaml
+++ b/swagger-def/definitions/prime/v3/PPMShipment.yaml
@@ -62,6 +62,12 @@ properties:
type: boolean
x-omitempty: false
x-nullable: true
+ tertiaryPickupAddress:
+ $ref: '../../Address.yaml'
+ hasTertiaryPickupAddress:
+ type: boolean
+ x-omitempty: false
+ x-nullable: true
actualPickupPostalCode:
description: >
The actual postal code where the PPM shipment started. To be filled once the customer has moved the shipment.
@@ -80,6 +86,12 @@ properties:
type: boolean
x-omitempty: false
x-nullable: true
+ tertiaryDestinationAddress:
+ $ref: '../../Address.yaml'
+ hasTertiaryDestinationAddress:
+ type: boolean
+ x-omitempty: false
+ x-nullable: true
actualDestinationPostalCode:
description: >
The actual postal code where the PPM shipment ended. To be filled once the customer has moved the shipment.
diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml
index 09bbc9c1f90..4b60dc576c2 100644
--- a/swagger-def/ghc.yaml
+++ b/swagger-def/ghc.yaml
@@ -3267,6 +3267,7 @@ paths:
closeoutInitiated,
closeoutLocation,
ppmStatus,
+ counselingOffice,
]
description: field that results should be sorted by
- in: query
@@ -3286,6 +3287,10 @@ paths:
name: lastName
type: string
description: filters using a prefix match on the service member's last name
+ - in: query
+ name: counselingOffice
+ type: string
+ description: filters using a counselingOffice name of the move
- in: query
name: dodID
type: string
@@ -4402,7 +4407,7 @@ definitions:
type: string
format: uuid
example: c56a4180-65aa-42ec-a945-5fd21dec0538
- dodID:
+ edipi:
type: string
userID:
type: string
@@ -6700,6 +6705,9 @@ definitions:
ppmStatus:
$ref: '#/definitions/PPMStatus'
x-nullable: true
+ counselingOffice:
+ type: string
+ x-nullable: true
QueueMovesResult:
type: object
properties:
diff --git a/swagger-def/internal.yaml b/swagger-def/internal.yaml
index 679236a358b..134d05f587a 100644
--- a/swagger-def/internal.yaml
+++ b/swagger-def/internal.yaml
@@ -705,7 +705,7 @@ definitions:
secondary_telephone:
type: string
format: telephone
- pattern: '^[2-9]\d{2}-\d{3}-\d{4}$'
+ pattern: '^([2-9]\d{2}-\d{3}-\d{4})?$'
example: 212-555-5555
x-nullable: true
title: Secondary Phone
@@ -799,7 +799,7 @@ definitions:
secondary_telephone:
type: string
format: telephone
- pattern: '^[2-9]\d{2}-\d{3}-\d{4}$'
+ pattern: '^([2-9]\d{2}-\d{3}-\d{4})?$'
example: 212-555-5555
x-nullable: true
title: Alternate phone
@@ -883,7 +883,7 @@ definitions:
secondary_telephone:
type: string
format: telephone
- pattern: '^[2-9]\d{2}-\d{3}-\d{4}$'
+ pattern: '^([2-9]\d{2}-\d{3}-\d{4})?$'
example: 212-555-5555
x-nullable: true
title: Alternate Phone
diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml
index 4440a907dc3..ce67ccb909d 100644
--- a/swagger/ghc.yaml
+++ b/swagger/ghc.yaml
@@ -3388,6 +3388,7 @@ paths:
- closeoutInitiated
- closeoutLocation
- ppmStatus
+ - counselingOffice
description: field that results should be sorted by
- in: query
name: order
@@ -3408,6 +3409,10 @@ paths:
name: lastName
type: string
description: filters using a prefix match on the service member's last name
+ - in: query
+ name: counselingOffice
+ type: string
+ description: filters using a counselingOffice name of the move
- in: query
name: dodID
type: string
@@ -4594,7 +4599,7 @@ definitions:
type: string
format: uuid
example: c56a4180-65aa-42ec-a945-5fd21dec0538
- dodID:
+ edipi:
type: string
userID:
type: string
@@ -6982,6 +6987,9 @@ definitions:
ppmStatus:
$ref: '#/definitions/PPMStatus'
x-nullable: true
+ counselingOffice:
+ type: string
+ x-nullable: true
QueueMovesResult:
type: object
properties:
@@ -10307,6 +10315,7 @@ definitions:
- StandaloneCrate
- StandaloneCrateCap
- UncappedRequestTotal
+ - LockedPriceCents
ServiceItemParamType:
type: string
enum:
diff --git a/swagger/internal.yaml b/swagger/internal.yaml
index cc75bf8e2fa..80c0dd139ed 100644
--- a/swagger/internal.yaml
+++ b/swagger/internal.yaml
@@ -726,7 +726,7 @@ definitions:
secondary_telephone:
type: string
format: telephone
- pattern: ^[2-9]\d{2}-\d{3}-\d{4}$
+ pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$
example: 212-555-5555
x-nullable: true
title: Secondary Phone
@@ -820,7 +820,7 @@ definitions:
secondary_telephone:
type: string
format: telephone
- pattern: ^[2-9]\d{2}-\d{3}-\d{4}$
+ pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$
example: 212-555-5555
x-nullable: true
title: Alternate phone
@@ -904,7 +904,7 @@ definitions:
secondary_telephone:
type: string
format: telephone
- pattern: ^[2-9]\d{2}-\d{3}-\d{4}$
+ pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$
example: 212-555-5555
x-nullable: true
title: Alternate Phone
diff --git a/swagger/prime.yaml b/swagger/prime.yaml
index f7f587b8624..6da8e27e845 100644
--- a/swagger/prime.yaml
+++ b/swagger/prime.yaml
@@ -3126,6 +3126,7 @@ definitions:
- StandaloneCrate
- StandaloneCrateCap
- UncappedRequestTotal
+ - LockedPriceCents
ServiceItemParamType:
type: string
enum:
diff --git a/swagger/prime_v2.yaml b/swagger/prime_v2.yaml
index 7e1bf6c0acc..def5f5626c3 100644
--- a/swagger/prime_v2.yaml
+++ b/swagger/prime_v2.yaml
@@ -1711,6 +1711,7 @@ definitions:
- StandaloneCrate
- StandaloneCrateCap
- UncappedRequestTotal
+ - LockedPriceCents
ServiceItemParamType:
type: string
enum:
diff --git a/swagger/prime_v3.yaml b/swagger/prime_v3.yaml
index 19edac26bea..77007b46f99 100644
--- a/swagger/prime_v3.yaml
+++ b/swagger/prime_v3.yaml
@@ -1735,6 +1735,7 @@ definitions:
- StandaloneCrate
- StandaloneCrateCap
- UncappedRequestTotal
+ - LockedPriceCents
ServiceItemParamType:
type: string
enum:
@@ -2304,6 +2305,12 @@ definitions:
type: boolean
x-omitempty: false
x-nullable: true
+ tertiaryPickupAddress:
+ $ref: '#/definitions/Address'
+ hasTertiaryPickupAddress:
+ type: boolean
+ x-omitempty: false
+ x-nullable: true
actualPickupPostalCode:
description: >
The actual postal code where the PPM shipment started. To be filled
@@ -2323,6 +2330,12 @@ definitions:
type: boolean
x-omitempty: false
x-nullable: true
+ tertiaryDestinationAddress:
+ $ref: '#/definitions/Address'
+ hasTertiaryDestinationAddress:
+ type: boolean
+ x-omitempty: false
+ x-nullable: true
actualDestinationPostalCode:
description: >
The actual postal code where the PPM shipment ended. To be filled once
@@ -2726,17 +2739,13 @@ definitions:
destinationType:
$ref: '#/definitions/DestinationType'
secondaryPickupAddress:
- description: >-
- A second pickup address for this shipment, if the customer entered
- one. An optional field.
- allOf:
- - $ref: '#/definitions/Address'
+ $ref: '#/definitions/Address'
secondaryDeliveryAddress:
- description: >-
- A second delivery address for this shipment, if the customer entered
- one. An optional field.
- allOf:
- - $ref: '#/definitions/Address'
+ $ref: '#/definitions/Address'
+ tertiaryPickupAddress:
+ $ref: '#/definitions/Address'
+ tertiaryDeliveryAddress:
+ $ref: '#/definitions/Address'
storageFacility:
allOf:
- x-nullable: true