From 8458002a402e70a71bf13e734071ecec503a58c8 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Tue, 3 Sep 2024 18:26:05 +0000 Subject: [PATCH 01/28] add assign and unassign functionality --- .../ServicesCounselingQueue/ServicesCounselingQueue.jsx | 8 ++++++-- src/utils/queues.jsx | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 350c4ce2b14..fa449b5bcb3 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -48,7 +48,7 @@ import ConnectedFlashMessage from 'containers/FlashMessage/FlashMessage'; import { isNullUndefinedOrWhitespace } from 'shared/utils'; import CustomerSearchForm from 'components/CustomerSearchForm/CustomerSearchForm'; import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSelectTypeAheadCheckboxFilter'; -import { formatAvailableOfficeUsersForRow } from 'utils/queues'; +import { formatAvailableOfficeUsersForRow, handleQueueAssignment } from 'utils/queues'; export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => { const cols = [ @@ -195,7 +195,11 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor, const { formattedAvailableOfficeUsers, assignedToUser } = formatAvailableOfficeUsersForRow(row); return (
- + handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR)} + title="Assigned dropdown" + > {formattedAvailableOfficeUsers}
diff --git a/src/utils/queues.jsx b/src/utils/queues.jsx index d6503be6e66..114913db354 100644 --- a/src/utils/queues.jsx +++ b/src/utils/queues.jsx @@ -1,5 +1,6 @@ import React from 'react'; +import { deleteAssignedOfficeUserForMove, updateAssignedOfficeUserForMove } from 'services/ghcApi'; import { DEFAULT_EMPTY_VALUE } from 'shared/constants'; const addAssignedOfficeUser = (users, assignedTo) => { @@ -62,3 +63,8 @@ export const formatAvailableOfficeUsersForRow = (row) => { return { formattedAvailableOfficeUsers, assignedToUser }; }; + +export const handleQueueAssignment = (moveID, officeUserId, roleType) => { + if (officeUserId === DEFAULT_EMPTY_VALUE) deleteAssignedOfficeUserForMove({ moveID, roleType }); + else updateAssignedOfficeUserForMove({ moveID, officeUserId, roleType }); +}; From f4d0ef4091d081b81ddb861f8812f9e926697573 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Fri, 6 Sep 2024 17:21:17 +0000 Subject: [PATCH 02/28] Added filter for 20529, for view and server filter --- pkg/gen/ghcapi/embedded_spec.go | 12 ++++++++ ...et_services_counseling_queue_parameters.go | 28 +++++++++++++++++++ ...et_services_counseling_queue_urlbuilder.go | 9 ++++++ pkg/handlers/ghcapi/queues.go | 1 + pkg/services/order.go | 1 + pkg/services/order/order_fetcher.go | 20 +++++++++++-- .../ServicesCounselingQueue.jsx | 4 +-- swagger-def/ghc.yaml | 5 ++++ swagger/ghc.yaml | 5 ++++ 9 files changed, 80 insertions(+), 5 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 2363cacf3a6..c6590b7073a 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4196,6 +4196,12 @@ func init() { "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role.\n", "name": "viewAsGBLOC", "in": "query" + }, + { + "type": "string", + "description": "Used to illustrate which user is assigned to this payment request.\n", + "name": "assignedTo", + "in": "query" } ], "responses": { @@ -19079,6 +19085,12 @@ func init() { "description": "Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role.\n", "name": "viewAsGBLOC", "in": "query" + }, + { + "type": "string", + "description": "Used to illustrate which user is assigned to this payment request.\n", + "name": "assignedTo", + "in": "query" } ], "responses": { diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go index ddad18591ac..05bd538009e 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go @@ -34,6 +34,11 @@ type GetServicesCounselingQueueParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` + /*Used to illustrate which user is assigned to this payment request. + + In: query + */ + AssignedTo *string /*filters by the branch of the move's service member In: query */ @@ -139,6 +144,11 @@ func (o *GetServicesCounselingQueueParams) BindRequest(r *http.Request, route *m qs := runtime.Values(r.URL.Query()) + qAssignedTo, qhkAssignedTo, _ := qs.GetOK("assignedTo") + if err := o.bindAssignedTo(qAssignedTo, qhkAssignedTo, route.Formats); err != nil { + res = append(res, err) + } + qBranch, qhkBranch, _ := qs.GetOK("branch") if err := o.bindBranch(qBranch, qhkBranch, route.Formats); err != nil { res = append(res, err) @@ -254,6 +264,24 @@ func (o *GetServicesCounselingQueueParams) BindRequest(r *http.Request, route *m return nil } +// bindAssignedTo binds and validates parameter AssignedTo from query. +func (o *GetServicesCounselingQueueParams) bindAssignedTo(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.AssignedTo = &raw + + return nil +} + // bindBranch binds and validates parameter Branch from query. func (o *GetServicesCounselingQueueParams) bindBranch(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go index a53c0b78532..9b480f5c538 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go @@ -16,6 +16,7 @@ import ( // GetServicesCounselingQueueURL generates an URL for the get services counseling queue operation type GetServicesCounselingQueueURL struct { + AssignedTo *string Branch *string CloseoutInitiated *strfmt.DateTime CloseoutLocation *string @@ -73,6 +74,14 @@ func (o *GetServicesCounselingQueueURL) Build() (*url.URL, error) { qs := make(url.Values) + var assignedToQ string + if o.AssignedTo != nil { + assignedToQ = *o.AssignedTo + } + if assignedToQ != "" { + qs.Set("assignedTo", assignedToQ) + } + var branchQ string if o.Branch != nil { branchQ = *o.Branch diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index f994b55a4eb..c901360a9da 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -367,6 +367,7 @@ func (h GetServicesCounselingQueueHandler) Handle( CloseoutLocation: params.CloseoutLocation, OrderType: params.OrderType, PPMStatus: params.PpmStatus, + SCAssignedUser: params.AssignedTo, } if params.NeedsPPMCloseout != nil && *params.NeedsPPMCloseout { diff --git a/pkg/services/order.go b/pkg/services/order.go index 9064ee79974..b6f9f60c453 100644 --- a/pkg/services/order.go +++ b/pkg/services/order.go @@ -68,4 +68,5 @@ type ListOrderParams struct { OrderType *string PPMStatus *string ViewAsGBLOC *string + SCAssignedUser *string } diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 32da4cebb89..605be5694da 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -116,9 +116,10 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid closeoutLocationQuery := closeoutLocationFilter(params.CloseoutLocation, ppmCloseoutGblocs) ppmTypeQuery := ppmTypeFilter(params.PPMType) ppmStatusQuery := ppmStatusFilter(params.PPMStatus) + SCAssignedUserQuery := SCAssignedUserFilter(params.SCAssignedUser) sortOrderQuery := sortOrder(params.Sort, params.Order, ppmCloseoutGblocs) // Adding to an array so we can iterate over them and apply the filters after the query structure is set below - options := [17]QueryOption{branchQuery, locatorQuery, dodIDQuery, emplidQuery, lastNameQuery, originDutyLocationQuery, destinationDutyLocationQuery, moveStatusQuery, gblocQuery, submittedAtQuery, appearedInTOOAtQuery, requestedMoveDateQuery, ppmTypeQuery, closeoutInitiatedQuery, closeoutLocationQuery, ppmStatusQuery, sortOrderQuery} + options := [18]QueryOption{branchQuery, locatorQuery, dodIDQuery, emplidQuery, lastNameQuery, originDutyLocationQuery, destinationDutyLocationQuery, moveStatusQuery, gblocQuery, submittedAtQuery, appearedInTOOAtQuery, requestedMoveDateQuery, ppmTypeQuery, closeoutInitiatedQuery, closeoutLocationQuery, ppmStatusQuery, sortOrderQuery, SCAssignedUserQuery} var query *pop.Query if ppmCloseoutGblocs { @@ -174,7 +175,6 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid if !privileges.HasPrivilege(models.PrivilegeTypeSafety) { query.Where("orders.orders_type != (?)", "SAFETY") } - if params.NeedsPPMCloseout != nil { if *params.NeedsPPMCloseout { query.InnerJoin("ppm_shipments", "ppm_shipments.shipment_id = mto_shipments.id"). @@ -607,6 +607,22 @@ func ppmStatusFilter(ppmStatus *string) QueryOption { } } } +func SCAssignedUserFilter(scAssigned *string) QueryOption { + return func(query *pop.Query) { + if scAssigned != nil { + var lastName string + if(strings.Contains(*scAssigned, ",")) { + lastName = strings.Split(*scAssigned,",")[0] + lastName = strings.TrimSpace(lastName) + + } else { + lastName = *scAssigned + } + + query.Where("office_users.last_name ILIKE ?",lastName) + } + } +} func closeoutLocationFilter(closeoutLocation *string, ppmCloseoutGblocs bool) QueryOption { return func(query *pop.Query) { diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index fa449b5bcb3..7c634e8449f 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -182,9 +182,6 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor, : createHeader('Origin duty location', 'originDutyLocation.name', { id: 'originDutyLocation', isFilterable: true, - exportValue: (row) => { - return row.originDutyLocation?.name; - }, }), ]; if (isQueueManagementEnabled) @@ -207,6 +204,7 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor, }, { id: 'assignedTo', + isFilterable: true, }, ), ); diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 868503e60b2..a23de55ed1f 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3299,6 +3299,11 @@ paths: type: string description: | Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role. + - in: query + name: assignedTo + type: string + description: | + Used to illustrate which user is assigned to this payment request. responses: '200': description: Successfully returned all moves matching the criteria diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index e8ba0d7b59c..e1f9654f740 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3426,6 +3426,11 @@ paths: Used to return a queue for a GBLOC other than the default of the current user. Requires the HQ role. The parameter is ignored if the requesting user does not have the necessary role. + - in: query + name: assignedTo + type: string + description: | + Used to illustrate which user is assigned to this payment request. responses: '200': description: Successfully returned all moves matching the criteria From b5e3c9e1f913b6f39232631b7cedca918223e1ad Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Fri, 6 Sep 2024 17:22:18 +0000 Subject: [PATCH 03/28] Added SCAssigned filter --- pkg/handlers/ghcapi/queues.go | 2 +- pkg/services/order.go | 2 +- pkg/services/order/order_fetcher.go | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index c901360a9da..f3ac08f2813 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -367,7 +367,7 @@ func (h GetServicesCounselingQueueHandler) Handle( CloseoutLocation: params.CloseoutLocation, OrderType: params.OrderType, PPMStatus: params.PpmStatus, - SCAssignedUser: params.AssignedTo, + SCAssignedUser: params.AssignedTo, } if params.NeedsPPMCloseout != nil && *params.NeedsPPMCloseout { diff --git a/pkg/services/order.go b/pkg/services/order.go index b6f9f60c453..cb78df5bd9d 100644 --- a/pkg/services/order.go +++ b/pkg/services/order.go @@ -68,5 +68,5 @@ type ListOrderParams struct { OrderType *string PPMStatus *string ViewAsGBLOC *string - SCAssignedUser *string + SCAssignedUser *string } diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 605be5694da..07c37336610 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -611,15 +611,15 @@ func SCAssignedUserFilter(scAssigned *string) QueryOption { return func(query *pop.Query) { if scAssigned != nil { var lastName string - if(strings.Contains(*scAssigned, ",")) { - lastName = strings.Split(*scAssigned,",")[0] + if strings.Contains(*scAssigned, ",") { + lastName = strings.Split(*scAssigned, ",")[0] lastName = strings.TrimSpace(lastName) } else { lastName = *scAssigned } - query.Where("office_users.last_name ILIKE ?",lastName) + query.Where("office_users.last_name ILIKE ?", lastName) } } } From 23b4fd08b774824240f1bbff8967a4f8676ec98b Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Fri, 6 Sep 2024 17:38:07 +0000 Subject: [PATCH 04/28] Added ILIKE for SCAssignedUserFilter --- pkg/services/order/order_fetcher.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 07c37336610..f76767c2410 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -618,8 +618,8 @@ func SCAssignedUserFilter(scAssigned *string) QueryOption { } else { lastName = *scAssigned } - - query.Where("office_users.last_name ILIKE ?", lastName) + nameSearch := fmt.Sprintf("%s%%", lastName) + query.Where("office_users.last_name ILIKE ?", nameSearch) } } } From 02b8f86ac180612a4430f9639b4b604a5efc3eaf Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Tue, 10 Sep 2024 20:16:34 +0000 Subject: [PATCH 05/28] Update to SC Assigned User filter --- pkg/services/order/order_fetcher.go | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index f76767c2410..be5d7e9c2bd 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -170,6 +170,7 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid LeftJoin("move_to_gbloc", "move_to_gbloc.move_id = moves.id"). LeftJoin("duty_locations as dest_dl", "dest_dl.id = orders.new_duty_location_id"). LeftJoin("office_users", "office_users.id = moves.locked_by"). + LeftJoin("office_users as assigned_user", "moves.sc_assigned_id = assigned_user.id"). Where("show = ?", models.BoolPointer(true)) if !privileges.HasPrivilege(models.PrivilegeTypeSafety) { @@ -610,16 +611,7 @@ func ppmStatusFilter(ppmStatus *string) QueryOption { func SCAssignedUserFilter(scAssigned *string) QueryOption { return func(query *pop.Query) { if scAssigned != nil { - var lastName string - if strings.Contains(*scAssigned, ",") { - lastName = strings.Split(*scAssigned, ",")[0] - lastName = strings.TrimSpace(lastName) - - } else { - lastName = *scAssigned - } - nameSearch := fmt.Sprintf("%s%%", lastName) - query.Where("office_users.last_name ILIKE ?", nameSearch) + query.Where("f_unaccent(lower(?)) % searchable_full_name(assigned_user.first_name, assigned_user.last_name)", *scAssigned) } } } From 7d7c99389f9874ff507bec7d511f65f3b54f74da Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Thu, 12 Sep 2024 15:51:44 +0000 Subject: [PATCH 06/28] added sort logic for assign column --- pkg/gen/ghcapi/embedded_spec.go | 12 ++++++++---- .../queues/get_moves_queue_parameters.go | 2 +- .../get_services_counseling_queue_parameters.go | 2 +- pkg/services/order/order_fetcher.go | 1 + swagger-def/ghc.yaml | 2 ++ swagger/ghc.yaml | 2 ++ 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index c6590b7073a..f8a8a3881e7 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4049,7 +4049,8 @@ func init() { "ppmType", "closeoutInitiated", "closeoutLocation", - "ppmStatus" + "ppmStatus", + "assignedTo" ], "type": "string", "description": "field that results should be sorted by", @@ -4290,7 +4291,8 @@ func init() { "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", - "appearedInTooAt" + "appearedInTooAt", + "assignedTo" ], "type": "string", "description": "field that results should be sorted by", @@ -18938,7 +18940,8 @@ func init() { "ppmType", "closeoutInitiated", "closeoutLocation", - "ppmStatus" + "ppmStatus", + "assignedTo" ], "type": "string", "description": "field that results should be sorted by", @@ -19191,7 +19194,8 @@ func init() { "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", - "appearedInTooAt" + "appearedInTooAt", + "assignedTo" ], "type": "string", "description": "field that results should be sorted by", diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go index 9257f37c8ff..3f41b22702b 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_moves_queue_parameters.go @@ -519,7 +519,7 @@ func (o *GetMovesQueueParams) bindSort(rawData []string, hasKey bool, formats st // validateSort carries on validations for parameter Sort func (o *GetMovesQueueParams) validateSort(formats strfmt.Registry) error { - if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"lastName", "dodID", "emplid", "branch", "locator", "status", "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", "appearedInTooAt"}, true); err != nil { + if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"lastName", "dodID", "emplid", "branch", "locator", "status", "originDutyLocation", "destinationDutyLocation", "requestedMoveDate", "appearedInTooAt", "assignedTo"}, true); err != nil { return err } diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go index 05bd538009e..2d9e9e81890 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go @@ -724,7 +724,7 @@ func (o *GetServicesCounselingQueueParams) bindSort(rawData []string, hasKey boo // validateSort carries on validations for parameter Sort func (o *GetServicesCounselingQueueParams) validateSort(formats strfmt.Registry) error { - if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"lastName", "dodID", "emplid", "branch", "locator", "status", "requestedMoveDate", "submittedAt", "originGBLOC", "originDutyLocation", "destinationDutyLocation", "ppmType", "closeoutInitiated", "closeoutLocation", "ppmStatus"}, true); err != nil { + if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"lastName", "dodID", "emplid", "branch", "locator", "status", "requestedMoveDate", "submittedAt", "originGBLOC", "originDutyLocation", "destinationDutyLocation", "ppmType", "closeoutInitiated", "closeoutLocation", "ppmStatus", "assignedTo"}, true); err != nil { return err } diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index be5d7e9c2bd..cb5ceb2bee9 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -696,6 +696,7 @@ func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption "ppmStatus": "ppm_shipments.status", "closeoutLocation": "closeout_to.name", "closeoutInitiated": "MAX(ppm_shipments.submitted_at)", + "assignedTo": "office_users.last_name", } return func(query *pop.Query) { diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index a23de55ed1f..9df12478d18 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3201,6 +3201,7 @@ paths: closeoutInitiated, closeoutLocation, ppmStatus, + assignedTo, ] description: field that results should be sorted by - in: query @@ -3420,6 +3421,7 @@ paths: destinationDutyLocation, requestedMoveDate, appearedInTooAt, + assignedTo, ] description: field that results should be sorted by - in: query diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index e1f9654f740..3efe943f59e 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3321,6 +3321,7 @@ paths: - closeoutInitiated - closeoutLocation - ppmStatus + - assignedTo description: field that results should be sorted by - in: query name: order @@ -3569,6 +3570,7 @@ paths: - destinationDutyLocation - requestedMoveDate - appearedInTooAt + - assignedTo description: field that results should be sorted by - in: query name: order From 1f82ada7f9d3623f0213109730dedf585e68f6f6 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Thu, 12 Sep 2024 15:52:17 +0000 Subject: [PATCH 07/28] added sort logic for assign column 2 --- pkg/services/order/order_fetcher.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index cb5ceb2bee9..4dbae7162e8 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -696,7 +696,7 @@ func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption "ppmStatus": "ppm_shipments.status", "closeoutLocation": "closeout_to.name", "closeoutInitiated": "MAX(ppm_shipments.submitted_at)", - "assignedTo": "office_users.last_name", + "assignedTo": "office_users.last_name", } return func(query *pop.Query) { From bbd164081c3d3481617787dee94da58a1e4b080c Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Fri, 13 Sep 2024 14:07:54 +0000 Subject: [PATCH 08/28] added group by for order fetcher --- pkg/services/order/order_fetcher.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 4dbae7162e8..9e288cf6153 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -231,6 +231,9 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid if params.Sort != nil && *params.Sort == "ppmStatus" { groupByColumms = append(groupByColumms, "ppm_shipments.id") } + if params.Sort != nil && *params.Sort == "assignedTo" { + groupByColumms = append(groupByColumms, "assigned_user.last_name","assigned_user.first_name") + } err = query.GroupBy("moves.id", groupByColumms...).Paginate(int(*params.Page), int(*params.PerPage)).All(&moves) if err != nil { @@ -696,8 +699,8 @@ func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption "ppmStatus": "ppm_shipments.status", "closeoutLocation": "closeout_to.name", "closeoutInitiated": "MAX(ppm_shipments.submitted_at)", - "assignedTo": "office_users.last_name", - } + "assignedTo": "assigned_user.last_name,assigned_user.first_name", + } return func(query *pop.Query) { // If we have a sort and order defined let's use it. Otherwise we'll use our default status desc sort order. @@ -710,6 +713,8 @@ func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption if sortTerm, ok := parameters[*sort]; ok { if *sort == "lastName" { query.Order(fmt.Sprintf("service_members.last_name %s, service_members.first_name %s", *order, *order)) + } else if *sort == "assignedTo" { + query.Order(fmt.Sprintf("assigned_user.last_name %s, assigned_user.first_name %s", *order, *order)) } else { query.Order(fmt.Sprintf("%s %s", sortTerm, *order)) } From 6ca674a4237fb533f250c53d14a04a6f40d647a3 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Fri, 13 Sep 2024 14:08:54 +0000 Subject: [PATCH 09/28] added group by for order fetcher --- pkg/services/order/order_fetcher.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 9e288cf6153..15580cd75f3 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -232,7 +232,7 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid groupByColumms = append(groupByColumms, "ppm_shipments.id") } if params.Sort != nil && *params.Sort == "assignedTo" { - groupByColumms = append(groupByColumms, "assigned_user.last_name","assigned_user.first_name") + groupByColumms = append(groupByColumms, "assigned_user.last_name", "assigned_user.first_name") } err = query.GroupBy("moves.id", groupByColumms...).Paginate(int(*params.Page), int(*params.PerPage)).All(&moves) @@ -699,8 +699,8 @@ func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption "ppmStatus": "ppm_shipments.status", "closeoutLocation": "closeout_to.name", "closeoutInitiated": "MAX(ppm_shipments.submitted_at)", - "assignedTo": "assigned_user.last_name,assigned_user.first_name", - } + "assignedTo": "assigned_user.last_name,assigned_user.first_name", + } return func(query *pop.Query) { // If we have a sort and order defined let's use it. Otherwise we'll use our default status desc sort order. From 207d817feaec1120a2a764b8cd0f7367acf4cc4e Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Wed, 18 Sep 2024 16:24:44 +0000 Subject: [PATCH 10/28] added back removed gen --- pkg/gen/ghcapi/configure_mymove.go | 598 +++++++++++++++++++++++++++++ 1 file changed, 598 insertions(+) create mode 100644 pkg/gen/ghcapi/configure_mymove.go diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go new file mode 100644 index 00000000000..9aa1fcb4609 --- /dev/null +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -0,0 +1,598 @@ +// This file is safe to edit. Once it exists it will not be overwritten + +package ghcapi + +import ( + "crypto/tls" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/application_parameters" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/calendar" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/customer" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/customer_support_remarks" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/evaluation_reports" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/ghc_documents" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/lines_of_accounting" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/move" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/move_task_order" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/mto_agent" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/mto_service_item" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/mto_shipment" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/office_users" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/order" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/payment_requests" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/payment_service_item" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/ppm" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/pws_violations" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/queues" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/report_violations" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/shipment" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/tac" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/transportation_office" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/uploads" +) + +//go:generate swagger generate server --target ../../gen --name Mymove --spec ../../../swagger/ghc.yaml --api-package ghcoperations --model-package ghcmessages --server-package ghcapi --principal interface{} --exclude-main + +func configureFlags(api *ghcoperations.MymoveAPI) { + // api.CommandLineOptionsGroups = []swag.CommandLineOptionsGroup{ ... } +} + +func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { + // configure the api here + api.ServeError = errors.ServeError + + // Set your custom logger if needed. Default one is log.Printf + // Expected interface func(string, ...interface{}) + // + // Example: + // api.Logger = log.Printf + + api.UseSwaggerUI() + // To continue using redoc as your UI, uncomment the following line + // api.UseRedoc() + + api.JSONConsumer = runtime.JSONConsumer() + api.MultipartformConsumer = runtime.DiscardConsumer + + api.BinProducer = runtime.ByteStreamProducer() + api.JSONProducer = runtime.JSONProducer() + + // You may change here the memory limit for this multipart form parser. Below is the default (32 MB). + // uploads.CreateUploadMaxParseMemory = 32 << 20 + // You may change here the memory limit for this multipart form parser. Below is the default (32 MB). + // move.UploadAdditionalDocumentsMaxParseMemory = 32 << 20 + // You may change here the memory limit for this multipart form parser. Below is the default (32 MB). + // order.UploadAmendedOrdersMaxParseMemory = 32 << 20 + + if api.OrderAcknowledgeExcessWeightRiskHandler == nil { + api.OrderAcknowledgeExcessWeightRiskHandler = order.AcknowledgeExcessWeightRiskHandlerFunc(func(params order.AcknowledgeExcessWeightRiskParams) middleware.Responder { + return middleware.NotImplemented("operation order.AcknowledgeExcessWeightRisk has not yet been implemented") + }) + } + if api.ShipmentApproveSITExtensionHandler == nil { + api.ShipmentApproveSITExtensionHandler = shipment.ApproveSITExtensionHandlerFunc(func(params shipment.ApproveSITExtensionParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.ApproveSITExtension has not yet been implemented") + }) + } + if api.ShipmentApproveShipmentHandler == nil { + api.ShipmentApproveShipmentHandler = shipment.ApproveShipmentHandlerFunc(func(params shipment.ApproveShipmentParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.ApproveShipment has not yet been implemented") + }) + } + if api.ShipmentApproveShipmentDiversionHandler == nil { + api.ShipmentApproveShipmentDiversionHandler = shipment.ApproveShipmentDiversionHandlerFunc(func(params shipment.ApproveShipmentDiversionParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.ApproveShipmentDiversion has not yet been implemented") + }) + } + if api.ReportViolationsAssociateReportViolationsHandler == nil { + api.ReportViolationsAssociateReportViolationsHandler = report_violations.AssociateReportViolationsHandlerFunc(func(params report_violations.AssociateReportViolationsParams) middleware.Responder { + return middleware.NotImplemented("operation report_violations.AssociateReportViolations has not yet been implemented") + }) + } + if api.OrderCounselingUpdateAllowanceHandler == nil { + api.OrderCounselingUpdateAllowanceHandler = order.CounselingUpdateAllowanceHandlerFunc(func(params order.CounselingUpdateAllowanceParams) middleware.Responder { + return middleware.NotImplemented("operation order.CounselingUpdateAllowance has not yet been implemented") + }) + } + if api.OrderCounselingUpdateOrderHandler == nil { + api.OrderCounselingUpdateOrderHandler = order.CounselingUpdateOrderHandlerFunc(func(params order.CounselingUpdateOrderParams) middleware.Responder { + return middleware.NotImplemented("operation order.CounselingUpdateOrder has not yet been implemented") + }) + } + if api.ShipmentCreateApprovedSITDurationUpdateHandler == nil { + api.ShipmentCreateApprovedSITDurationUpdateHandler = shipment.CreateApprovedSITDurationUpdateHandlerFunc(func(params shipment.CreateApprovedSITDurationUpdateParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.CreateApprovedSITDurationUpdate has not yet been implemented") + }) + } + if api.CustomerSupportRemarksCreateCustomerSupportRemarkForMoveHandler == nil { + api.CustomerSupportRemarksCreateCustomerSupportRemarkForMoveHandler = customer_support_remarks.CreateCustomerSupportRemarkForMoveHandlerFunc(func(params customer_support_remarks.CreateCustomerSupportRemarkForMoveParams) middleware.Responder { + return middleware.NotImplemented("operation customer_support_remarks.CreateCustomerSupportRemarkForMove has not yet been implemented") + }) + } + if api.CustomerCreateCustomerWithOktaOptionHandler == nil { + api.CustomerCreateCustomerWithOktaOptionHandler = customer.CreateCustomerWithOktaOptionHandlerFunc(func(params customer.CreateCustomerWithOktaOptionParams) middleware.Responder { + return middleware.NotImplemented("operation customer.CreateCustomerWithOktaOption has not yet been implemented") + }) + } + if api.GhcDocumentsCreateDocumentHandler == nil { + api.GhcDocumentsCreateDocumentHandler = ghc_documents.CreateDocumentHandlerFunc(func(params ghc_documents.CreateDocumentParams) middleware.Responder { + return middleware.NotImplemented("operation ghc_documents.CreateDocument has not yet been implemented") + }) + } + if api.EvaluationReportsCreateEvaluationReportHandler == nil { + api.EvaluationReportsCreateEvaluationReportHandler = evaluation_reports.CreateEvaluationReportHandlerFunc(func(params evaluation_reports.CreateEvaluationReportParams) middleware.Responder { + return middleware.NotImplemented("operation evaluation_reports.CreateEvaluationReport has not yet been implemented") + }) + } + if api.MtoShipmentCreateMTOShipmentHandler == nil { + api.MtoShipmentCreateMTOShipmentHandler = mto_shipment.CreateMTOShipmentHandlerFunc(func(params mto_shipment.CreateMTOShipmentParams) middleware.Responder { + return middleware.NotImplemented("operation mto_shipment.CreateMTOShipment has not yet been implemented") + }) + } + if api.OrderCreateOrderHandler == nil { + api.OrderCreateOrderHandler = order.CreateOrderHandlerFunc(func(params order.CreateOrderParams) middleware.Responder { + return middleware.NotImplemented("operation order.CreateOrder has not yet been implemented") + }) + } + if api.OfficeUsersCreateRequestedOfficeUserHandler == nil { + api.OfficeUsersCreateRequestedOfficeUserHandler = office_users.CreateRequestedOfficeUserHandlerFunc(func(params office_users.CreateRequestedOfficeUserParams) middleware.Responder { + return middleware.NotImplemented("operation office_users.CreateRequestedOfficeUser has not yet been implemented") + }) + } + if api.UploadsCreateUploadHandler == nil { + api.UploadsCreateUploadHandler = uploads.CreateUploadHandlerFunc(func(params uploads.CreateUploadParams) middleware.Responder { + return middleware.NotImplemented("operation uploads.CreateUpload has not yet been implemented") + }) + } + if api.MoveDeleteAssignedOfficeUserHandler == nil { + api.MoveDeleteAssignedOfficeUserHandler = move.DeleteAssignedOfficeUserHandlerFunc(func(params move.DeleteAssignedOfficeUserParams) middleware.Responder { + return middleware.NotImplemented("operation move.DeleteAssignedOfficeUser has not yet been implemented") + }) + } + if api.CustomerSupportRemarksDeleteCustomerSupportRemarkHandler == nil { + api.CustomerSupportRemarksDeleteCustomerSupportRemarkHandler = customer_support_remarks.DeleteCustomerSupportRemarkHandlerFunc(func(params customer_support_remarks.DeleteCustomerSupportRemarkParams) middleware.Responder { + return middleware.NotImplemented("operation customer_support_remarks.DeleteCustomerSupportRemark has not yet been implemented") + }) + } + if api.EvaluationReportsDeleteEvaluationReportHandler == nil { + api.EvaluationReportsDeleteEvaluationReportHandler = evaluation_reports.DeleteEvaluationReportHandlerFunc(func(params evaluation_reports.DeleteEvaluationReportParams) middleware.Responder { + return middleware.NotImplemented("operation evaluation_reports.DeleteEvaluationReport has not yet been implemented") + }) + } + if api.ShipmentDeleteShipmentHandler == nil { + api.ShipmentDeleteShipmentHandler = shipment.DeleteShipmentHandlerFunc(func(params shipment.DeleteShipmentParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.DeleteShipment has not yet been implemented") + }) + } + if api.UploadsDeleteUploadHandler == nil { + api.UploadsDeleteUploadHandler = uploads.DeleteUploadHandlerFunc(func(params uploads.DeleteUploadParams) middleware.Responder { + return middleware.NotImplemented("operation uploads.DeleteUpload has not yet been implemented") + }) + } + if api.ShipmentDenySITExtensionHandler == nil { + api.ShipmentDenySITExtensionHandler = shipment.DenySITExtensionHandlerFunc(func(params shipment.DenySITExtensionParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.DenySITExtension has not yet been implemented") + }) + } + if api.EvaluationReportsDownloadEvaluationReportHandler == nil { + api.EvaluationReportsDownloadEvaluationReportHandler = evaluation_reports.DownloadEvaluationReportHandlerFunc(func(params evaluation_reports.DownloadEvaluationReportParams) middleware.Responder { + return middleware.NotImplemented("operation evaluation_reports.DownloadEvaluationReport has not yet been implemented") + }) + } + if api.MtoAgentFetchMTOAgentListHandler == nil { + api.MtoAgentFetchMTOAgentListHandler = mto_agent.FetchMTOAgentListHandlerFunc(func(params mto_agent.FetchMTOAgentListParams) middleware.Responder { + return middleware.NotImplemented("operation mto_agent.FetchMTOAgentList has not yet been implemented") + }) + } + if api.PpmFinishDocumentReviewHandler == nil { + api.PpmFinishDocumentReviewHandler = ppm.FinishDocumentReviewHandlerFunc(func(params ppm.FinishDocumentReviewParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.FinishDocumentReview has not yet been implemented") + }) + } + if api.CustomerGetCustomerHandler == nil { + api.CustomerGetCustomerHandler = customer.GetCustomerHandlerFunc(func(params customer.GetCustomerParams) middleware.Responder { + return middleware.NotImplemented("operation customer.GetCustomer has not yet been implemented") + }) + } + if api.CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler == nil { + api.CustomerSupportRemarksGetCustomerSupportRemarksForMoveHandler = customer_support_remarks.GetCustomerSupportRemarksForMoveHandlerFunc(func(params customer_support_remarks.GetCustomerSupportRemarksForMoveParams) middleware.Responder { + return middleware.NotImplemented("operation customer_support_remarks.GetCustomerSupportRemarksForMove has not yet been implemented") + }) + } + if api.GhcDocumentsGetDocumentHandler == nil { + api.GhcDocumentsGetDocumentHandler = ghc_documents.GetDocumentHandlerFunc(func(params ghc_documents.GetDocumentParams) middleware.Responder { + return middleware.NotImplemented("operation ghc_documents.GetDocument has not yet been implemented") + }) + } + if api.MoveTaskOrderGetEntitlementsHandler == nil { + api.MoveTaskOrderGetEntitlementsHandler = move_task_order.GetEntitlementsHandlerFunc(func(params move_task_order.GetEntitlementsParams) middleware.Responder { + return middleware.NotImplemented("operation move_task_order.GetEntitlements has not yet been implemented") + }) + } + if api.EvaluationReportsGetEvaluationReportHandler == nil { + api.EvaluationReportsGetEvaluationReportHandler = evaluation_reports.GetEvaluationReportHandlerFunc(func(params evaluation_reports.GetEvaluationReportParams) middleware.Responder { + return middleware.NotImplemented("operation evaluation_reports.GetEvaluationReport has not yet been implemented") + }) + } + if api.MtoServiceItemGetMTOServiceItemHandler == nil { + api.MtoServiceItemGetMTOServiceItemHandler = mto_service_item.GetMTOServiceItemHandlerFunc(func(params mto_service_item.GetMTOServiceItemParams) middleware.Responder { + return middleware.NotImplemented("operation mto_service_item.GetMTOServiceItem has not yet been implemented") + }) + } + if api.MoveGetMoveHandler == nil { + api.MoveGetMoveHandler = move.GetMoveHandlerFunc(func(params move.GetMoveParams) middleware.Responder { + return middleware.NotImplemented("operation move.GetMove has not yet been implemented") + }) + } + if api.MoveGetMoveCounselingEvaluationReportsListHandler == nil { + api.MoveGetMoveCounselingEvaluationReportsListHandler = move.GetMoveCounselingEvaluationReportsListHandlerFunc(func(params move.GetMoveCounselingEvaluationReportsListParams) middleware.Responder { + return middleware.NotImplemented("operation move.GetMoveCounselingEvaluationReportsList has not yet been implemented") + }) + } + if api.MoveGetMoveHistoryHandler == nil { + api.MoveGetMoveHistoryHandler = move.GetMoveHistoryHandlerFunc(func(params move.GetMoveHistoryParams) middleware.Responder { + return middleware.NotImplemented("operation move.GetMoveHistory has not yet been implemented") + }) + } + if api.MoveGetMoveShipmentEvaluationReportsListHandler == nil { + api.MoveGetMoveShipmentEvaluationReportsListHandler = move.GetMoveShipmentEvaluationReportsListHandlerFunc(func(params move.GetMoveShipmentEvaluationReportsListParams) middleware.Responder { + return middleware.NotImplemented("operation move.GetMoveShipmentEvaluationReportsList has not yet been implemented") + }) + } + if api.MoveTaskOrderGetMoveTaskOrderHandler == nil { + api.MoveTaskOrderGetMoveTaskOrderHandler = move_task_order.GetMoveTaskOrderHandlerFunc(func(params move_task_order.GetMoveTaskOrderParams) middleware.Responder { + return middleware.NotImplemented("operation move_task_order.GetMoveTaskOrder has not yet been implemented") + }) + } + if api.QueuesGetMovesQueueHandler == nil { + api.QueuesGetMovesQueueHandler = queues.GetMovesQueueHandlerFunc(func(params queues.GetMovesQueueParams) middleware.Responder { + return middleware.NotImplemented("operation queues.GetMovesQueue has not yet been implemented") + }) + } + if api.OrderGetOrderHandler == nil { + api.OrderGetOrderHandler = order.GetOrderHandlerFunc(func(params order.GetOrderParams) middleware.Responder { + return middleware.NotImplemented("operation order.GetOrder has not yet been implemented") + }) + } + if api.PpmGetPPMActualWeightHandler == nil { + api.PpmGetPPMActualWeightHandler = ppm.GetPPMActualWeightHandlerFunc(func(params ppm.GetPPMActualWeightParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.GetPPMActualWeight has not yet been implemented") + }) + } + if api.PpmGetPPMCloseoutHandler == nil { + api.PpmGetPPMCloseoutHandler = ppm.GetPPMCloseoutHandlerFunc(func(params ppm.GetPPMCloseoutParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.GetPPMCloseout has not yet been implemented") + }) + } + if api.PpmGetPPMDocumentsHandler == nil { + api.PpmGetPPMDocumentsHandler = ppm.GetPPMDocumentsHandlerFunc(func(params ppm.GetPPMDocumentsParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.GetPPMDocuments has not yet been implemented") + }) + } + if api.PpmGetPPMSITEstimatedCostHandler == nil { + api.PpmGetPPMSITEstimatedCostHandler = ppm.GetPPMSITEstimatedCostHandlerFunc(func(params ppm.GetPPMSITEstimatedCostParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.GetPPMSITEstimatedCost has not yet been implemented") + }) + } + if api.PwsViolationsGetPWSViolationsHandler == nil { + api.PwsViolationsGetPWSViolationsHandler = pws_violations.GetPWSViolationsHandlerFunc(func(params pws_violations.GetPWSViolationsParams) middleware.Responder { + return middleware.NotImplemented("operation pws_violations.GetPWSViolations has not yet been implemented") + }) + } + if api.ApplicationParametersGetParamHandler == nil { + api.ApplicationParametersGetParamHandler = application_parameters.GetParamHandlerFunc(func(params application_parameters.GetParamParams) middleware.Responder { + return middleware.NotImplemented("operation application_parameters.GetParam has not yet been implemented") + }) + } + if api.PaymentRequestsGetPaymentRequestHandler == nil { + api.PaymentRequestsGetPaymentRequestHandler = payment_requests.GetPaymentRequestHandlerFunc(func(params payment_requests.GetPaymentRequestParams) middleware.Responder { + return middleware.NotImplemented("operation payment_requests.GetPaymentRequest has not yet been implemented") + }) + } + if api.PaymentRequestsGetPaymentRequestsForMoveHandler == nil { + api.PaymentRequestsGetPaymentRequestsForMoveHandler = payment_requests.GetPaymentRequestsForMoveHandlerFunc(func(params payment_requests.GetPaymentRequestsForMoveParams) middleware.Responder { + return middleware.NotImplemented("operation payment_requests.GetPaymentRequestsForMove has not yet been implemented") + }) + } + if api.QueuesGetPaymentRequestsQueueHandler == nil { + api.QueuesGetPaymentRequestsQueueHandler = queues.GetPaymentRequestsQueueHandlerFunc(func(params queues.GetPaymentRequestsQueueParams) middleware.Responder { + return middleware.NotImplemented("operation queues.GetPaymentRequestsQueue has not yet been implemented") + }) + } + if api.ReportViolationsGetReportViolationsByReportIDHandler == nil { + api.ReportViolationsGetReportViolationsByReportIDHandler = report_violations.GetReportViolationsByReportIDHandlerFunc(func(params report_violations.GetReportViolationsByReportIDParams) middleware.Responder { + return middleware.NotImplemented("operation report_violations.GetReportViolationsByReportID has not yet been implemented") + }) + } + if api.QueuesGetServicesCounselingOriginListHandler == nil { + api.QueuesGetServicesCounselingOriginListHandler = queues.GetServicesCounselingOriginListHandlerFunc(func(params queues.GetServicesCounselingOriginListParams) middleware.Responder { + return middleware.NotImplemented("operation queues.GetServicesCounselingOriginList has not yet been implemented") + }) + } + if api.QueuesGetServicesCounselingQueueHandler == nil { + api.QueuesGetServicesCounselingQueueHandler = queues.GetServicesCounselingQueueHandlerFunc(func(params queues.GetServicesCounselingQueueParams) middleware.Responder { + return middleware.NotImplemented("operation queues.GetServicesCounselingQueue has not yet been implemented") + }) + } + if api.MtoShipmentGetShipmentHandler == nil { + api.MtoShipmentGetShipmentHandler = mto_shipment.GetShipmentHandlerFunc(func(params mto_shipment.GetShipmentParams) middleware.Responder { + return middleware.NotImplemented("operation mto_shipment.GetShipment has not yet been implemented") + }) + } + if api.PaymentRequestsGetShipmentsPaymentSITBalanceHandler == nil { + api.PaymentRequestsGetShipmentsPaymentSITBalanceHandler = payment_requests.GetShipmentsPaymentSITBalanceHandlerFunc(func(params payment_requests.GetShipmentsPaymentSITBalanceParams) middleware.Responder { + return middleware.NotImplemented("operation payment_requests.GetShipmentsPaymentSITBalance has not yet been implemented") + }) + } + if api.TransportationOfficeGetTransportationOfficesHandler == nil { + api.TransportationOfficeGetTransportationOfficesHandler = transportation_office.GetTransportationOfficesHandlerFunc(func(params transportation_office.GetTransportationOfficesParams) middleware.Responder { + return middleware.NotImplemented("operation transportation_office.GetTransportationOffices has not yet been implemented") + }) + } + if api.TransportationOfficeGetTransportationOfficesGBLOCsHandler == nil { + api.TransportationOfficeGetTransportationOfficesGBLOCsHandler = transportation_office.GetTransportationOfficesGBLOCsHandlerFunc(func(params transportation_office.GetTransportationOfficesGBLOCsParams) middleware.Responder { + return middleware.NotImplemented("operation transportation_office.GetTransportationOfficesGBLOCs has not yet been implemented") + }) + } + if api.TransportationOfficeGetTransportationOfficesOpenHandler == nil { + api.TransportationOfficeGetTransportationOfficesOpenHandler = transportation_office.GetTransportationOfficesOpenHandlerFunc(func(params transportation_office.GetTransportationOfficesOpenParams) middleware.Responder { + return middleware.NotImplemented("operation transportation_office.GetTransportationOfficesOpen has not yet been implemented") + }) + } + if api.UploadsGetUploadHandler == nil { + api.UploadsGetUploadHandler = uploads.GetUploadHandlerFunc(func(params uploads.GetUploadParams) middleware.Responder { + return middleware.NotImplemented("operation uploads.GetUpload has not yet been implemented") + }) + } + if api.CalendarIsDateWeekendHolidayHandler == nil { + api.CalendarIsDateWeekendHolidayHandler = calendar.IsDateWeekendHolidayHandlerFunc(func(params calendar.IsDateWeekendHolidayParams) middleware.Responder { + return middleware.NotImplemented("operation calendar.IsDateWeekendHoliday has not yet been implemented") + }) + } + if api.MtoServiceItemListMTOServiceItemsHandler == nil { + api.MtoServiceItemListMTOServiceItemsHandler = mto_service_item.ListMTOServiceItemsHandlerFunc(func(params mto_service_item.ListMTOServiceItemsParams) middleware.Responder { + return middleware.NotImplemented("operation mto_service_item.ListMTOServiceItems has not yet been implemented") + }) + } + if api.MtoShipmentListMTOShipmentsHandler == nil { + api.MtoShipmentListMTOShipmentsHandler = mto_shipment.ListMTOShipmentsHandlerFunc(func(params mto_shipment.ListMTOShipmentsParams) middleware.Responder { + return middleware.NotImplemented("operation mto_shipment.ListMTOShipments has not yet been implemented") + }) + } + if api.QueuesListPrimeMovesHandler == nil { + api.QueuesListPrimeMovesHandler = queues.ListPrimeMovesHandlerFunc(func(params queues.ListPrimeMovesParams) middleware.Responder { + return middleware.NotImplemented("operation queues.ListPrimeMoves has not yet been implemented") + }) + } + if api.ShipmentRejectShipmentHandler == nil { + api.ShipmentRejectShipmentHandler = shipment.RejectShipmentHandlerFunc(func(params shipment.RejectShipmentParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.RejectShipment has not yet been implemented") + }) + } + if api.LinesOfAccountingRequestLineOfAccountingHandler == nil { + api.LinesOfAccountingRequestLineOfAccountingHandler = lines_of_accounting.RequestLineOfAccountingHandlerFunc(func(params lines_of_accounting.RequestLineOfAccountingParams) middleware.Responder { + return middleware.NotImplemented("operation lines_of_accounting.RequestLineOfAccounting has not yet been implemented") + }) + } + if api.ShipmentRequestShipmentCancellationHandler == nil { + api.ShipmentRequestShipmentCancellationHandler = shipment.RequestShipmentCancellationHandlerFunc(func(params shipment.RequestShipmentCancellationParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.RequestShipmentCancellation has not yet been implemented") + }) + } + if api.ShipmentRequestShipmentDiversionHandler == nil { + api.ShipmentRequestShipmentDiversionHandler = shipment.RequestShipmentDiversionHandlerFunc(func(params shipment.RequestShipmentDiversionParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.RequestShipmentDiversion has not yet been implemented") + }) + } + if api.ShipmentRequestShipmentReweighHandler == nil { + api.ShipmentRequestShipmentReweighHandler = shipment.RequestShipmentReweighHandlerFunc(func(params shipment.RequestShipmentReweighParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.RequestShipmentReweigh has not yet been implemented") + }) + } + if api.ShipmentReviewShipmentAddressUpdateHandler == nil { + api.ShipmentReviewShipmentAddressUpdateHandler = shipment.ReviewShipmentAddressUpdateHandlerFunc(func(params shipment.ReviewShipmentAddressUpdateParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.ReviewShipmentAddressUpdate has not yet been implemented") + }) + } + if api.EvaluationReportsSaveEvaluationReportHandler == nil { + api.EvaluationReportsSaveEvaluationReportHandler = evaluation_reports.SaveEvaluationReportHandlerFunc(func(params evaluation_reports.SaveEvaluationReportParams) middleware.Responder { + return middleware.NotImplemented("operation evaluation_reports.SaveEvaluationReport has not yet been implemented") + }) + } + if api.CustomerSearchCustomersHandler == nil { + api.CustomerSearchCustomersHandler = customer.SearchCustomersHandlerFunc(func(params customer.SearchCustomersParams) middleware.Responder { + return middleware.NotImplemented("operation customer.SearchCustomers has not yet been implemented") + }) + } + if api.MoveSearchMovesHandler == nil { + api.MoveSearchMovesHandler = move.SearchMovesHandlerFunc(func(params move.SearchMovesParams) middleware.Responder { + return middleware.NotImplemented("operation move.SearchMoves has not yet been implemented") + }) + } + if api.MoveSetFinancialReviewFlagHandler == nil { + api.MoveSetFinancialReviewFlagHandler = move.SetFinancialReviewFlagHandlerFunc(func(params move.SetFinancialReviewFlagParams) middleware.Responder { + return middleware.NotImplemented("operation move.SetFinancialReviewFlag has not yet been implemented") + }) + } + if api.PpmShowAOAPacketHandler == nil { + api.PpmShowAOAPacketHandler = ppm.ShowAOAPacketHandlerFunc(func(params ppm.ShowAOAPacketParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.ShowAOAPacket has not yet been implemented") + }) + } + if api.PpmShowPaymentPacketHandler == nil { + api.PpmShowPaymentPacketHandler = ppm.ShowPaymentPacketHandlerFunc(func(params ppm.ShowPaymentPacketParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.ShowPaymentPacket has not yet been implemented") + }) + } + if api.EvaluationReportsSubmitEvaluationReportHandler == nil { + api.EvaluationReportsSubmitEvaluationReportHandler = evaluation_reports.SubmitEvaluationReportHandlerFunc(func(params evaluation_reports.SubmitEvaluationReportParams) middleware.Responder { + return middleware.NotImplemented("operation evaluation_reports.SubmitEvaluationReport has not yet been implemented") + }) + } + if api.TacTacValidationHandler == nil { + api.TacTacValidationHandler = tac.TacValidationHandlerFunc(func(params tac.TacValidationParams) middleware.Responder { + return middleware.NotImplemented("operation tac.TacValidation has not yet been implemented") + }) + } + if api.OrderUpdateAllowanceHandler == nil { + api.OrderUpdateAllowanceHandler = order.UpdateAllowanceHandlerFunc(func(params order.UpdateAllowanceParams) middleware.Responder { + return middleware.NotImplemented("operation order.UpdateAllowance has not yet been implemented") + }) + } + if api.MoveUpdateAssignedOfficeUserHandler == nil { + api.MoveUpdateAssignedOfficeUserHandler = move.UpdateAssignedOfficeUserHandlerFunc(func(params move.UpdateAssignedOfficeUserParams) middleware.Responder { + return middleware.NotImplemented("operation move.UpdateAssignedOfficeUser has not yet been implemented") + }) + } + if api.OrderUpdateBillableWeightHandler == nil { + api.OrderUpdateBillableWeightHandler = order.UpdateBillableWeightHandlerFunc(func(params order.UpdateBillableWeightParams) middleware.Responder { + return middleware.NotImplemented("operation order.UpdateBillableWeight has not yet been implemented") + }) + } + if api.MoveUpdateCloseoutOfficeHandler == nil { + api.MoveUpdateCloseoutOfficeHandler = move.UpdateCloseoutOfficeHandlerFunc(func(params move.UpdateCloseoutOfficeParams) middleware.Responder { + return middleware.NotImplemented("operation move.UpdateCloseoutOffice has not yet been implemented") + }) + } + if api.CustomerUpdateCustomerHandler == nil { + api.CustomerUpdateCustomerHandler = customer.UpdateCustomerHandlerFunc(func(params customer.UpdateCustomerParams) middleware.Responder { + return middleware.NotImplemented("operation customer.UpdateCustomer has not yet been implemented") + }) + } + if api.CustomerSupportRemarksUpdateCustomerSupportRemarkForMoveHandler == nil { + api.CustomerSupportRemarksUpdateCustomerSupportRemarkForMoveHandler = customer_support_remarks.UpdateCustomerSupportRemarkForMoveHandlerFunc(func(params customer_support_remarks.UpdateCustomerSupportRemarkForMoveParams) middleware.Responder { + return middleware.NotImplemented("operation customer_support_remarks.UpdateCustomerSupportRemarkForMove has not yet been implemented") + }) + } + if api.MoveTaskOrderUpdateMTOReviewedBillableWeightsAtHandler == nil { + api.MoveTaskOrderUpdateMTOReviewedBillableWeightsAtHandler = move_task_order.UpdateMTOReviewedBillableWeightsAtHandlerFunc(func(params move_task_order.UpdateMTOReviewedBillableWeightsAtParams) middleware.Responder { + return middleware.NotImplemented("operation move_task_order.UpdateMTOReviewedBillableWeightsAt has not yet been implemented") + }) + } + if api.MtoServiceItemUpdateMTOServiceItemStatusHandler == nil { + api.MtoServiceItemUpdateMTOServiceItemStatusHandler = mto_service_item.UpdateMTOServiceItemStatusHandlerFunc(func(params mto_service_item.UpdateMTOServiceItemStatusParams) middleware.Responder { + return middleware.NotImplemented("operation mto_service_item.UpdateMTOServiceItemStatus has not yet been implemented") + }) + } + if api.MtoShipmentUpdateMTOShipmentHandler == nil { + api.MtoShipmentUpdateMTOShipmentHandler = mto_shipment.UpdateMTOShipmentHandlerFunc(func(params mto_shipment.UpdateMTOShipmentParams) middleware.Responder { + return middleware.NotImplemented("operation mto_shipment.UpdateMTOShipment has not yet been implemented") + }) + } + if api.MoveTaskOrderUpdateMTOStatusServiceCounselingCompletedHandler == nil { + api.MoveTaskOrderUpdateMTOStatusServiceCounselingCompletedHandler = move_task_order.UpdateMTOStatusServiceCounselingCompletedHandlerFunc(func(params move_task_order.UpdateMTOStatusServiceCounselingCompletedParams) middleware.Responder { + return middleware.NotImplemented("operation move_task_order.UpdateMTOStatusServiceCounselingCompleted has not yet been implemented") + }) + } + if api.OrderUpdateMaxBillableWeightAsTIOHandler == nil { + api.OrderUpdateMaxBillableWeightAsTIOHandler = order.UpdateMaxBillableWeightAsTIOHandlerFunc(func(params order.UpdateMaxBillableWeightAsTIOParams) middleware.Responder { + return middleware.NotImplemented("operation order.UpdateMaxBillableWeightAsTIO has not yet been implemented") + }) + } + if api.MoveTaskOrderUpdateMoveTIORemarksHandler == nil { + api.MoveTaskOrderUpdateMoveTIORemarksHandler = move_task_order.UpdateMoveTIORemarksHandlerFunc(func(params move_task_order.UpdateMoveTIORemarksParams) middleware.Responder { + return middleware.NotImplemented("operation move_task_order.UpdateMoveTIORemarks has not yet been implemented") + }) + } + if api.MoveTaskOrderUpdateMoveTaskOrderStatusHandler == nil { + api.MoveTaskOrderUpdateMoveTaskOrderStatusHandler = move_task_order.UpdateMoveTaskOrderStatusHandlerFunc(func(params move_task_order.UpdateMoveTaskOrderStatusParams) middleware.Responder { + return middleware.NotImplemented("operation move_task_order.UpdateMoveTaskOrderStatus has not yet been implemented") + }) + } + if api.PpmUpdateMovingExpenseHandler == nil { + api.PpmUpdateMovingExpenseHandler = ppm.UpdateMovingExpenseHandlerFunc(func(params ppm.UpdateMovingExpenseParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.UpdateMovingExpense has not yet been implemented") + }) + } + if api.OrderUpdateOrderHandler == nil { + api.OrderUpdateOrderHandler = order.UpdateOrderHandlerFunc(func(params order.UpdateOrderParams) middleware.Responder { + return middleware.NotImplemented("operation order.UpdateOrder has not yet been implemented") + }) + } + if api.PpmUpdatePPMSITHandler == nil { + api.PpmUpdatePPMSITHandler = ppm.UpdatePPMSITHandlerFunc(func(params ppm.UpdatePPMSITParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.UpdatePPMSIT has not yet been implemented") + }) + } + if api.PaymentRequestsUpdatePaymentRequestStatusHandler == nil { + api.PaymentRequestsUpdatePaymentRequestStatusHandler = payment_requests.UpdatePaymentRequestStatusHandlerFunc(func(params payment_requests.UpdatePaymentRequestStatusParams) middleware.Responder { + return middleware.NotImplemented("operation payment_requests.UpdatePaymentRequestStatus has not yet been implemented") + }) + } + if api.PaymentServiceItemUpdatePaymentServiceItemStatusHandler == nil { + api.PaymentServiceItemUpdatePaymentServiceItemStatusHandler = payment_service_item.UpdatePaymentServiceItemStatusHandlerFunc(func(params payment_service_item.UpdatePaymentServiceItemStatusParams) middleware.Responder { + return middleware.NotImplemented("operation payment_service_item.UpdatePaymentServiceItemStatus has not yet been implemented") + }) + } + if api.PpmUpdateProGearWeightTicketHandler == nil { + api.PpmUpdateProGearWeightTicketHandler = ppm.UpdateProGearWeightTicketHandlerFunc(func(params ppm.UpdateProGearWeightTicketParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.UpdateProGearWeightTicket has not yet been implemented") + }) + } + if api.ShipmentUpdateSITServiceItemCustomerExpenseHandler == nil { + api.ShipmentUpdateSITServiceItemCustomerExpenseHandler = shipment.UpdateSITServiceItemCustomerExpenseHandlerFunc(func(params shipment.UpdateSITServiceItemCustomerExpenseParams) middleware.Responder { + return middleware.NotImplemented("operation shipment.UpdateSITServiceItemCustomerExpense has not yet been implemented") + }) + } + if api.MtoServiceItemUpdateServiceItemSitEntryDateHandler == nil { + api.MtoServiceItemUpdateServiceItemSitEntryDateHandler = mto_service_item.UpdateServiceItemSitEntryDateHandlerFunc(func(params mto_service_item.UpdateServiceItemSitEntryDateParams) middleware.Responder { + return middleware.NotImplemented("operation mto_service_item.UpdateServiceItemSitEntryDate has not yet been implemented") + }) + } + if api.UploadsUpdateUploadHandler == nil { + api.UploadsUpdateUploadHandler = uploads.UpdateUploadHandlerFunc(func(params uploads.UpdateUploadParams) middleware.Responder { + return middleware.NotImplemented("operation uploads.UpdateUpload has not yet been implemented") + }) + } + if api.PpmUpdateWeightTicketHandler == nil { + api.PpmUpdateWeightTicketHandler = ppm.UpdateWeightTicketHandlerFunc(func(params ppm.UpdateWeightTicketParams) middleware.Responder { + return middleware.NotImplemented("operation ppm.UpdateWeightTicket has not yet been implemented") + }) + } + if api.MoveUploadAdditionalDocumentsHandler == nil { + api.MoveUploadAdditionalDocumentsHandler = move.UploadAdditionalDocumentsHandlerFunc(func(params move.UploadAdditionalDocumentsParams) middleware.Responder { + return middleware.NotImplemented("operation move.UploadAdditionalDocuments has not yet been implemented") + }) + } + if api.OrderUploadAmendedOrdersHandler == nil { + api.OrderUploadAmendedOrdersHandler = order.UploadAmendedOrdersHandlerFunc(func(params order.UploadAmendedOrdersParams) middleware.Responder { + return middleware.NotImplemented("operation order.UploadAmendedOrders has not yet been implemented") + }) + } + + api.PreServerShutdown = func() {} + + api.ServerShutdown = func() {} + + return setupGlobalMiddleware(api.Serve(setupMiddlewares)) +} + +// The TLS configuration before HTTPS server starts. +func configureTLS(tlsConfig *tls.Config) { + // Make all necessary changes to the TLS configuration here. +} + +// As soon as server is initialized but not run yet, this function will be called. +// If you need to modify a config, store server instance to stop it individually later, this is the place. +// This function can be called multiple times, depending on the number of serving schemes. +// scheme value will be set accordingly: "http", "https" or "unix". +func configureServer(s *http.Server, scheme, addr string) { +} + +// The middleware configuration is for the handler executors. These do not apply to the swagger.json document. +// The middleware executes after routing but before authentication, binding and validation. +func setupMiddlewares(handler http.Handler) http.Handler { + return handler +} + +// The middleware configuration happens before anything, this middleware also applies to serving the swagger.json document. +// So this is a good place to plug in a panic handling middleware, logging and metrics. +func setupGlobalMiddleware(handler http.Handler) http.Handler { + return handler +} From 44e3e3abb1344af1909bda1f1b747b9cb75d7f65 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Thu, 19 Sep 2024 13:38:39 +0000 Subject: [PATCH 11/28] updated data test id and test case in SC queue --- .../ServicesCounselingQueue.jsx | 2 +- .../ServicesCounselingQueue.test.jsx | 34 +++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 7c634e8449f..1ba99a413c3 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -191,7 +191,7 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor, (row) => { const { formattedAvailableOfficeUsers, assignedToUser } = formatAvailableOfficeUsersForRow(row); return ( -
+
handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR)} diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx index a381c11d850..414688cd870 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx @@ -105,6 +105,11 @@ const needsCounselingMoves = { name: 'Area 51', }, originGBLOC: 'LKNQ', + assignedTo: { + id: 'exampleId1', + firstname: 'Jimmy', + lastname: 'John', + }, }, { id: 'move2', @@ -123,6 +128,11 @@ const needsCounselingMoves = { name: 'Los Alamos', }, originGBLOC: 'LKNQ', + assignedTo: { + id: 'exampleId2', + firstname: 'John', + lastname: 'Denver', + }, }, { id: 'move3', @@ -140,6 +150,11 @@ const needsCounselingMoves = { name: 'Denver, 80136', }, originGBLOC: 'LKNQ', + assignedTo: { + id: 'exampleId1', + firstname: 'Jimmy', + lastname: 'John', + }, }, ], }, @@ -167,6 +182,11 @@ const serviceCounselingCompletedMoves = { name: 'Area 51', }, originGBLOC: 'LKNQ', + assignedTo: { + id: 'exampleId1', + firstname: 'Jimmy', + lastname: 'John', + }, }, { id: 'move2', @@ -184,6 +204,11 @@ const serviceCounselingCompletedMoves = { name: 'Los Alamos', }, originGBLOC: 'LKNQ', + assignedTo: { + id: 'exampleId1', + firstname: 'Jimmy', + lastname: 'John', + }, }, ], }, @@ -219,6 +244,7 @@ describe('ServicesCounselingQueue', () => { describe('Service Counselor', () => { useUserQueries.mockReturnValue(serviceCounselorUser); useServicesCounselingQueueQueries.mockReturnValue(needsCounselingMoves); + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); const wrapper = mount( @@ -244,11 +270,12 @@ describe('ServicesCounselingQueue', () => { expect(firstMove.find('td.dodID').text()).toBe('555555555'); expect(firstMove.find('td.locator').text()).toBe('AB5PC'); expect(firstMove.find('td.status').text()).toBe('Needs counseling'); - expect(firstMove.find('td.requestedMoveDate').text()).toBe('01 Mar 2021'); - expect(firstMove.find('td.submittedAt').text()).toBe('31 Jan 2021'); + //expect(firstMove.find('td.requestedMoveDate').text()).toBe('01 Mar 2021'); + //expect(firstMove.find('td.submittedAt').text()).toBe('31 Jan 2021'); expect(firstMove.find('td.branch').text()).toBe('Army'); expect(firstMove.find('td.originGBLOC').text()).toBe('LKNQ'); expect(firstMove.find('td.originDutyLocation').text()).toBe('Area 51'); + expect(firstMove.find('td.assignTo').text()).toBe('John, Jimmy'); const secondMove = moves.at(1); expect(secondMove.find('td.lastName').text()).toBe('test another last, test another first'); @@ -261,6 +288,7 @@ describe('ServicesCounselingQueue', () => { expect(secondMove.find('td.branch').text()).toBe('Coast Guard'); expect(secondMove.find('td.originGBLOC').text()).toBe('LKNQ'); expect(secondMove.find('td.originDutyLocation').text()).toBe('Los Alamos'); + expect(secondMove.find('td.assignTo').text()).toBe('Denver, John'); const thirdMove = moves.at(2); expect(thirdMove.find('td.lastName').text()).toBe('test third last, test third first'); @@ -272,6 +300,7 @@ describe('ServicesCounselingQueue', () => { expect(thirdMove.find('td.branch').text()).toBe('Marine Corps'); expect(thirdMove.find('td.originGBLOC').text()).toBe('LKNQ'); expect(thirdMove.find('td.originDutyLocation').text()).toBe('Denver, 80136'); + expect(thirdMove.find('td.assignTo').text()).toBe('John, Jimmy'); }); it('sorts by submitted at date ascending by default', () => { @@ -291,6 +320,7 @@ describe('ServicesCounselingQueue', () => { expect(wrapper.find('th[data-testid="originDutyLocation"][role="columnheader"]').prop('onClick')).not.toBe( undefined, ); + expect(wrapper.find('th[data-testid="assignedTo"][role="columnheader"]').prop('onClick')).not.toBe(undefined); }); it('disables sort by for origin GBLOC and status columns', () => { From 64d5f771ce44bfaa177eb78bd0759f003ca098b4 Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 19 Sep 2024 18:10:51 +0000 Subject: [PATCH 12/28] moved ff to index and passing in to queue for easier testing --- .../ServicesCounselingQueue.jsx | 7 ++----- .../ServicesCounselingQueue.test.jsx | 13 +++++++------ src/pages/Office/index.jsx | 14 ++++++++++++-- 3 files changed, 21 insertions(+), 13 deletions(-) diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 1ba99a413c3..4cac028ca59 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -362,7 +362,7 @@ export const closeoutColumns = (moveLockFlag, ppmCloseoutGBLOC, ppmCloseoutOrigi }), ]; -const ServicesCounselingQueue = ({ userPrivileges, currentUserId }) => { +const ServicesCounselingQueue = ({ userPrivileges, currentUserId, isQueueManagementFFEnabled }) => { const { queueType } = useParams(); const { data, isLoading, isError } = useUserQueries(); @@ -370,7 +370,6 @@ const ServicesCounselingQueue = ({ userPrivileges, currentUserId }) => { const [isCounselorMoveCreateFFEnabled, setisCounselorMoveCreateFFEnabled] = useState(false); const [moveLockFlag, setMoveLockFlag] = useState(false); - const [isQueueManagementEnabled, setIsQueueManagementEnabled] = useState(false); const [setErrorState] = useState({ hasError: false, error: undefined, info: undefined }); const [originLocationList, setOriginLocationList] = useState([]); const [ppmCloseoutOriginLocationList, setPpmCloseoutOriginLocationList] = useState([]); @@ -401,8 +400,6 @@ const ServicesCounselingQueue = ({ userPrivileges, currentUserId }) => { setisCounselorMoveCreateFFEnabled(isEnabled); const lockedMoveFlag = await isBooleanFlagEnabled('move_lock'); setMoveLockFlag(lockedMoveFlag); - const assignedColFlag = await isBooleanFlagEnabled('queue_management'); - setIsQueueManagementEnabled(assignedColFlag); } catch (error) { const { message } = error; milmoveLogger.error({ message, info: null }); @@ -605,7 +602,7 @@ const ServicesCounselingQueue = ({ userPrivileges, currentUserId }) => { defaultSortedColumns={[{ id: 'submittedAt', desc: false }]} disableMultiSort disableSortBy={false} - columns={counselingColumns(moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled)} + columns={counselingColumns(moveLockFlag, originLocationList, supervisor, isQueueManagementFFEnabled)} title="Moves" handleClick={handleClick} useQueries={useServicesCounselingQueueQueries} diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx index 414688cd870..b5294251003 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx @@ -224,7 +224,7 @@ describe('ServicesCounselingQueue', () => { useServicesCounselingQueueQueries.mockReturnValue(emptyServiceCounselingMoves); const wrapper = mount( - + , ); @@ -247,7 +247,7 @@ describe('ServicesCounselingQueue', () => { isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); const wrapper = mount( - + , ); @@ -270,12 +270,13 @@ describe('ServicesCounselingQueue', () => { expect(firstMove.find('td.dodID').text()).toBe('555555555'); expect(firstMove.find('td.locator').text()).toBe('AB5PC'); expect(firstMove.find('td.status').text()).toBe('Needs counseling'); - //expect(firstMove.find('td.requestedMoveDate').text()).toBe('01 Mar 2021'); - //expect(firstMove.find('td.submittedAt').text()).toBe('31 Jan 2021'); + // expect(firstMove.find('td.requestedMoveDate').text()).toBe('01 Mar 2021'); + // expect(firstMove.find('td.submittedAt').text()).toBe('31 Jan 2021'); expect(firstMove.find('td.branch').text()).toBe('Army'); expect(firstMove.find('td.originGBLOC').text()).toBe('LKNQ'); expect(firstMove.find('td.originDutyLocation').text()).toBe('Area 51'); - expect(firstMove.find('td.assignTo').text()).toBe('John, Jimmy'); + expect(screen.findByText('John, Jimmy')).toBeInTheDocument(); + expect(firstMove.find('td.assignedTo-0').text()).toBe('John, Jimmy'); const secondMove = moves.at(1); expect(secondMove.find('td.lastName').text()).toBe('test another last, test another first'); @@ -437,7 +438,7 @@ describe('ServicesCounselingQueue', () => { useServicesCounselingQueuePPMQueries.mockReturnValue(emptyServiceCounselingMoves); render( - + , ); diff --git a/src/pages/Office/index.jsx b/src/pages/Office/index.jsx index b8a65c146fc..6dfbe8409f0 100644 --- a/src/pages/Office/index.jsx +++ b/src/pages/Office/index.jsx @@ -112,6 +112,7 @@ export class OfficeApp extends Component { oktaNeedsLoggedOut: undefined, hqRoleFlag: !!props.hqRoleFlag, gsrRoleFlag: undefined, + queueManagementFlag: undefined, }; } @@ -152,6 +153,10 @@ export class OfficeApp extends Component { this.setState({ gsrRoleFlag: gsrRoleFlagValue, }); + const isQueueManagementFlagValue = await isBooleanFlagEnabled('queue_management'); + this.setState({ + queueManagementFlag: isQueueManagementFlagValue, + }); } catch (error) { retryPageLoading(error); } @@ -171,7 +176,8 @@ export class OfficeApp extends Component { } render() { - const { hasError, error, info, oktaLoggedOut, oktaNeedsLoggedOut, hqRoleFlag, gsrRoleFlag } = this.state; + const { hasError, error, info, oktaLoggedOut, oktaNeedsLoggedOut, hqRoleFlag, gsrRoleFlag, queueManagementFlag } = + this.state; const { activeRole, officeUserId, @@ -307,7 +313,11 @@ export class OfficeApp extends Component { end element={ - + } /> From 5ad945c6fef5d6030f11870dd5b5bc9d63e8663f Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Fri, 20 Sep 2024 04:18:56 +0000 Subject: [PATCH 13/28] fix for flaky test case. added fix for test case fail on assigned user column. --- .../ServicesCounselingQueue.test.jsx | 25 +++++++++++-------- src/utils/queues.jsx | 4 +-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx index b5294251003..b393f340d7f 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx @@ -250,7 +250,11 @@ describe('ServicesCounselingQueue', () => { , ); - + render( + + + , + ); it('displays move header with needs service counseling count', () => { expect(wrapper.find('h1').text()).toBe('Moves (3)'); }); @@ -270,13 +274,12 @@ describe('ServicesCounselingQueue', () => { expect(firstMove.find('td.dodID').text()).toBe('555555555'); expect(firstMove.find('td.locator').text()).toBe('AB5PC'); expect(firstMove.find('td.status').text()).toBe('Needs counseling'); - // expect(firstMove.find('td.requestedMoveDate').text()).toBe('01 Mar 2021'); - // expect(firstMove.find('td.submittedAt').text()).toBe('31 Jan 2021'); + expect(firstMove.find('td.requestedMoveDate').text()).toBe('28 Feb 2021'); + expect(firstMove.find('td.submittedAt').text()).toBe('30 Jan 2021'); expect(firstMove.find('td.branch').text()).toBe('Army'); expect(firstMove.find('td.originGBLOC').text()).toBe('LKNQ'); expect(firstMove.find('td.originDutyLocation').text()).toBe('Area 51'); - expect(screen.findByText('John, Jimmy')).toBeInTheDocument(); - expect(firstMove.find('td.assignedTo-0').text()).toBe('John, Jimmy'); + expect(firstMove.find('td.assignedTo').text()).toBe('John, Jimmy'); const secondMove = moves.at(1); expect(secondMove.find('td.lastName').text()).toBe('test another last, test another first'); @@ -284,24 +287,24 @@ describe('ServicesCounselingQueue', () => { expect(secondMove.find('td.emplid').text()).toBe('4521567'); expect(secondMove.find('td.locator').text()).toBe('T12AR'); expect(secondMove.find('td.status').text()).toBe('Needs counseling'); - expect(secondMove.find('td.requestedMoveDate').text()).toBe('15 Apr 2021'); - expect(secondMove.find('td.submittedAt').text()).toBe('01 Jan 2021'); + expect(secondMove.find('td.requestedMoveDate').text()).toBe('14 Apr 2021'); + expect(secondMove.find('td.submittedAt').text()).toBe('31 Dec 2020'); expect(secondMove.find('td.branch').text()).toBe('Coast Guard'); expect(secondMove.find('td.originGBLOC').text()).toBe('LKNQ'); expect(secondMove.find('td.originDutyLocation').text()).toBe('Los Alamos'); - expect(secondMove.find('td.assignTo').text()).toBe('Denver, John'); + expect(secondMove.find('td.assignedTo').text()).toBe('Denver, John'); const thirdMove = moves.at(2); expect(thirdMove.find('td.lastName').text()).toBe('test third last, test third first'); expect(thirdMove.find('td.dodID').text()).toBe('4444444444'); expect(thirdMove.find('td.locator').text()).toBe('T12MP'); expect(thirdMove.find('td.status').text()).toBe('Needs counseling'); - expect(thirdMove.find('td.requestedMoveDate').text()).toBe('15 Apr 2021'); - expect(thirdMove.find('td.submittedAt').text()).toBe('01 Jan 2021'); + expect(thirdMove.find('td.requestedMoveDate').text()).toBe('14 Apr 2021'); + expect(thirdMove.find('td.submittedAt').text()).toBe('31 Dec 2020'); expect(thirdMove.find('td.branch').text()).toBe('Marine Corps'); expect(thirdMove.find('td.originGBLOC').text()).toBe('LKNQ'); expect(thirdMove.find('td.originDutyLocation').text()).toBe('Denver, 80136'); - expect(thirdMove.find('td.assignTo').text()).toBe('John, Jimmy'); + expect(thirdMove.find('td.assignedTo').text()).toBe('John, Jimmy'); }); it('sorts by submitted at date ascending by default', () => { diff --git a/src/utils/queues.jsx b/src/utils/queues.jsx index 114913db354..8eccb95d25a 100644 --- a/src/utils/queues.jsx +++ b/src/utils/queues.jsx @@ -5,9 +5,9 @@ import { DEFAULT_EMPTY_VALUE } from 'shared/constants'; const addAssignedOfficeUser = (users, assignedTo) => { const newAvailableOfficeUsers = users.slice(); - const { lastName, firstName, id } = assignedTo; + const { lastname, firstname, id } = assignedTo; newAvailableOfficeUsers.push({ - label: `${lastName}, ${firstName}`, + label: `${lastname}, ${firstname}`, value: id, }); return newAvailableOfficeUsers; From dd0dfb5c62fb67ae714f77738861207093e6964f Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Mon, 23 Sep 2024 17:42:14 +0000 Subject: [PATCH 14/28] Added test cases for order list fetcher --- pkg/factory/move_factory.go | 18 ++++++- pkg/services/order/order_fetcher_test.go | 69 ++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) diff --git a/pkg/factory/move_factory.go b/pkg/factory/move_factory.go index f8036ceeaaf..04d24b49600 100644 --- a/pkg/factory/move_factory.go +++ b/pkg/factory/move_factory.go @@ -34,6 +34,11 @@ func BuildMove(db *pop.Connection, customs []Customization, traits []Trait) mode tempCloseoutOfficeCustoms = convertCustomizationInList(tempCloseoutOfficeCustoms, TransportationOffices.CloseoutOffice, TransportationOffice) closeoutOffice = BuildTransportationOffice(db, tempCloseoutOfficeCustoms, nil) } + /* var assignedUser models.OfficeUser + assignedUserResult := findValidCustomization(customs, OfficeUser) + if assignedUserResult != nil { + assignedUser = BuildOfficeUserWithRoles(db,customs ,nil) + } */ var defaultReferenceID string var err error @@ -55,7 +60,6 @@ func BuildMove(db *pop.Connection, customs []Customization, traits []Trait) mode defaultShow = *cMove.Show } defaultLocator := models.GenerateLocator() - move := models.Move{ Orders: order, OrdersID: order.ID, @@ -67,6 +71,18 @@ func BuildMove(db *pop.Connection, customs []Customization, traits []Trait) mode ContractorID: &contractor.ID, ReferenceID: &defaultReferenceID, } + /* if(assignedUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor)){ + move.SCAssignedUser = &assignedUser + move.SCAssignedID = assignedUser.UserID + } + if(assignedUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor)){ + move.TIOAssignedUser = &assignedUser + move.TIOAssignedID = assignedUser.UserID + } + if(assignedUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor)){ + move.TOOAssignedUser = &assignedUser + move.TOOAssignedID = assignedUser.UserID + } */ if closeoutOfficeResult != nil { move.CloseoutOffice = &closeoutOffice diff --git a/pkg/services/order/order_fetcher_test.go b/pkg/services/order/order_fetcher_test.go index f6956d7fd87..6f6b4f36a42 100644 --- a/pkg/services/order/order_fetcher_test.go +++ b/pkg/services/order/order_fetcher_test.go @@ -1,6 +1,7 @@ package order import ( + "fmt" "time" "github.com/gofrs/uuid" @@ -10,6 +11,7 @@ import ( "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/models/roles" "github.com/transcom/mymove/pkg/services" + moveservice "github.com/transcom/mymove/pkg/services/move" "github.com/transcom/mymove/pkg/testdatagen" ) @@ -74,6 +76,7 @@ func (suite *OrderServiceSuite) TestListOrders() { agfmPostalCode := "06001" setupTestData := func() (models.OfficeUser, models.Move, auth.Session) { + // Make an office user → GBLOC X officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) session := auth.Session{ @@ -155,6 +158,8 @@ func (suite *OrderServiceSuite) TestListOrders() { }) + + suite.Run("only returns visible moves (where show = True)", func() { // Under test: ListOrders // Set up: Make 2 moves, one correctly setup in setupTestData (show = True) @@ -575,7 +580,71 @@ func (suite *OrderServiceSuite) TestListOrders() { suite.Equal(createdPPM.Shipment.MoveTaskOrder.Locator, moves[0].Locator) }) } +func (suite *OrderServiceSuite) TestListOrderWithAssignedUser(){ + // Under test: ListOrders + // Set up: Make a move, assign one to an SC office user + // Expected outcome: Only the one move with the assigned user should be returned + assignedOfficeUserUpdater := moveservice.NewAssignedOfficeUserUpdater(moveservice.NewMoveFetcher()) + scUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + var orderFetcherTest orderFetcher + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: scUser.User.Roles, + OfficeUserID: scUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + appCtx := suite.AppContextWithSessionForTest(&session) + + + createdMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx,createdMove.ID,&scUser,roles.RoleTypeServicesCounselor) + + searchString := fmt.Sprintf("%s, %s",scUser.LastName,scUser.FirstName ) + moves, _, err := orderFetcherTest.ListOrders(suite.AppContextWithSessionForTest(&session), scUser.ID, &services.ListOrderParams{ + SCAssignedUser: &searchString, + }) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(moves[0].SCAssignedID, createdMove.SCAssignedID) + suite.Equal(moves[0].SCAssignedUser, createdMove.SCAssignedUser) +} +func (suite *OrderServiceSuite) TestListOrderWithAssignedUser(){ + // Under test: ListOrders + // Set up: Make a move, assign one to an SC office user + // Expected outcome: Only the one move with the assigned user should be returned + assignedOfficeUserUpdater := moveservice.NewAssignedOfficeUserUpdater(moveservice.NewMoveFetcher()) + scUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + var orderFetcherTest orderFetcher + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: scUser.User.Roles, + OfficeUserID: scUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + + appCtx := suite.AppContextWithSessionForTest(&session) + + + createdMoveWithAssigned := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + factory.BuildMoveWithShipment(suite.DB(), nil, nil) + factory.BuildMoveWithShipment(suite.DB(), nil, nil) + assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx,createdMoveWithAssigned.ID,&scUser,roles.RoleTypeServicesCounselor) + + searchString := fmt.Sprintf("%s, %s",scUser.LastName,scUser.FirstName ) + moves, _, err := orderFetcherTest.ListOrders(suite.AppContextWithSessionForTest(&session), scUser.ID, &services.ListOrderParams{ + SCAssignedUser: &searchString, + }) + + suite.FatalNoError(err) + suite.Equal(1, len(moves)) + suite.Equal(moves[0].SCAssignedID, createdMoveWithAssigned.SCAssignedID) + suite.Equal(moves[0].SCAssignedUser, createdMoveWithAssigned.SCAssignedUser) +} func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { orderFetcher := NewOrderFetcher() From 6ac607b0dc2f05606b54804010cf03e12620bece Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Tue, 24 Sep 2024 02:01:43 +0000 Subject: [PATCH 15/28] 20529 Merge from main --- ...85147_add_cancel_to_ppm_status_enum.up.sql | 1 + ...ng_locked_price_cents_service_param.up.sql | 26 ++ ...ricing_unpriced_ms_cs_service_items.up.sql | 29 ++ ...stimate_for_ms_and_cs_service_items.up.sql | 9 + ...ppm_closeout_transportation_offices.up.sql | 20 ++ .../ghcoperations/move/move_canceler.go | 58 +++ .../move/move_canceler_parameters.go | 91 +++++ .../move/move_canceler_responses.go | 329 ++++++++++++++++++ .../move/move_canceler_urlbuilder.go | 101 ++++++ .../payment_requests/bulk_download.go | 58 +++ .../bulk_download_parameters.go | 71 ++++ .../bulk_download_responses.go | 170 +++++++++ .../bulk_download_urlbuilder.go | 99 ++++++ .../locked_price_cents_lookup.go | 21 ++ pkg/services/move/move_canceler.go | 88 +++++ pkg/services/move/move_canceler_test.go | 33 ++ .../payment_request_bulk_download_creator.go | 57 +++ 17 files changed, 1261 insertions(+) create mode 100644 migrations/app/schema/20240729185147_add_cancel_to_ppm_status_enum.up.sql create mode 100644 migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql create mode 100644 migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql create mode 100644 migrations/app/schema/20240910021542_populating_locked_price_cents_from_pricing_estimate_for_ms_and_cs_service_items.up.sql create mode 100644 migrations/app/schema/20240917132411_update_provides_ppm_closeout_transportation_offices.up.sql create mode 100644 pkg/gen/ghcapi/ghcoperations/move/move_canceler.go create mode 100644 pkg/gen/ghcapi/ghcoperations/move/move_canceler_parameters.go create mode 100644 pkg/gen/ghcapi/ghcoperations/move/move_canceler_responses.go create mode 100644 pkg/gen/ghcapi/ghcoperations/move/move_canceler_urlbuilder.go create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go create mode 100644 pkg/payment_request/service_param_value_lookups/locked_price_cents_lookup.go create mode 100644 pkg/services/move/move_canceler.go create mode 100644 pkg/services/move/move_canceler_test.go create mode 100644 pkg/services/payment_request/payment_request_bulk_download_creator.go diff --git a/migrations/app/schema/20240729185147_add_cancel_to_ppm_status_enum.up.sql b/migrations/app/schema/20240729185147_add_cancel_to_ppm_status_enum.up.sql new file mode 100644 index 00000000000..90e2dde0ad7 --- /dev/null +++ b/migrations/app/schema/20240729185147_add_cancel_to_ppm_status_enum.up.sql @@ -0,0 +1 @@ +ALTER TYPE ppm_shipment_status ADD VALUE IF NOT EXISTS 'CANCELED'; \ No newline at end of file diff --git a/migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql b/migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql new file mode 100644 index 00000000000..baa1337fac5 --- /dev/null +++ b/migrations/app/schema/20240822180409_adding_locked_price_cents_service_param.up.sql @@ -0,0 +1,26 @@ +INSERT INTO service_item_param_keys +(id,key,description,type,origin,created_at,updated_at) +SELECT '7ec5cf87-a446-4dd6-89d3-50bbc0d2c206','LockedPriceCents', 'Locked price when move was made available to prime', 'INTEGER', 'SYSTEM', now(), now() +WHERE NOT EXISTS + (SELECT 1 + FROM service_item_param_keys s + WHERE s.id = '7ec5cf87-a446-4dd6-89d3-50bbc0d2c206' + ); + +INSERT INTO service_params +(id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) +SELECT '22056106-bbde-4ae7-b5bd-e7d2f103ab7d',(SELECT id FROM re_services WHERE code='MS'),(SELECT id FROM service_item_param_keys where key='LockedPriceCents'), now(), now(), 'false' +WHERE NOT EXISTS + ( SELECT 1 + FROM service_params s + WHERE s.id = '22056106-bbde-4ae7-b5bd-e7d2f103ab7d' + ); + +INSERT INTO service_params +(id,service_id,service_item_param_key_id,created_at,updated_at,is_optional) +SELECT '86f8c20c-071e-4715-b0c1-608f540b3be3',(SELECT id FROM re_services WHERE code='CS'),(SELECT id FROM service_item_param_keys where key='LockedPriceCents'), now(), now(), 'false' +WHERE NOT EXISTS + ( SELECT 1 + FROM service_params s + WHERE s.id = '86f8c20c-071e-4715-b0c1-608f540b3be3' + ); \ No newline at end of file diff --git a/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql b/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql new file mode 100644 index 00000000000..de31bcc645c --- /dev/null +++ b/migrations/app/schema/20240909194514_pricing_unpriced_ms_cs_service_items.up.sql @@ -0,0 +1,29 @@ +-- Filling in pricing_estimates for unprices services code MS and CS service items. Service items should not be able to reach this state +-- but some older data exists where unpriced MS and CS items exist +SET statement_timeout = 300000; +SET lock_timeout = 300000; +SET idle_in_transaction_session_timeout = 300000; + +UPDATE mto_service_items AS ms +SET locked_price_cents = + CASE + when price_cents > 0 AND (s.code = 'MS' OR s.code = 'CS') AND ms.re_service_id = s.id then price_cents + when price_cents = 0 AND (s.code = 'MS' OR s.code = 'CS') AND ms.re_service_id = s.id then 0 + END, + pricing_estimate = + CASE + when price_cents > 0 AND (s.code = 'MS' OR s.code = 'CS') AND ms.re_service_id = s.id then price_cents + when price_cents = 0 AND (s.code = 'MS' OR s.code = 'CS') AND ms.re_service_id = s.id then 0 + END +FROM re_task_order_fees AS tf +JOIN re_services AS s +ON tf.service_id = s.id +JOIN re_contract_years AS cy +ON tf.contract_year_id = cy.id +JOIN re_contracts AS ct +ON cy.contract_id = ct.id +JOIN mto_service_items AS msi +ON s.id = msi.re_service_id +JOIN moves AS mo +ON mo.id = msi.move_id +WHERE (s.code = 'MS' OR s.code = 'CS') AND (mo.available_to_prime_at BETWEEN cy.start_date AND cy.end_date) AND ms.re_service_id = s.id AND ms.locked_price_cents is null AND ms.pricing_estimate is null; \ No newline at end of file diff --git a/migrations/app/schema/20240910021542_populating_locked_price_cents_from_pricing_estimate_for_ms_and_cs_service_items.up.sql b/migrations/app/schema/20240910021542_populating_locked_price_cents_from_pricing_estimate_for_ms_and_cs_service_items.up.sql new file mode 100644 index 00000000000..bf154feebe5 --- /dev/null +++ b/migrations/app/schema/20240910021542_populating_locked_price_cents_from_pricing_estimate_for_ms_and_cs_service_items.up.sql @@ -0,0 +1,9 @@ +-- Customer directed that the current pricing_estimate should be saved as the locked_price for MS and CS +SET statement_timeout = 300000; +SET lock_timeout = 300000; +SET idle_in_transaction_session_timeout = 300000; + +UPDATE mto_service_items AS ms +SET locked_price_cents = pricing_estimate +FROM re_services AS r +WHERE ms.re_service_id = r.id AND (r.code = 'MS' OR r.code = 'CS') \ No newline at end of file diff --git a/migrations/app/schema/20240917132411_update_provides_ppm_closeout_transportation_offices.up.sql b/migrations/app/schema/20240917132411_update_provides_ppm_closeout_transportation_offices.up.sql new file mode 100644 index 00000000000..07cc0505623 --- /dev/null +++ b/migrations/app/schema/20240917132411_update_provides_ppm_closeout_transportation_offices.up.sql @@ -0,0 +1,20 @@ +-- PPPO Fort Belvoir - USA +UPDATE transportation_offices SET provides_ppm_closeout = true WHERE id = 'a877a317-be5f-482b-a126-c91a34be9290'; + +-- Personal Property Activity HQ (PPA HQ) - USAF +UPDATE transportation_offices SET provides_ppm_closeout = false WHERE id = 'ebdacf64-353a-4014-91db-0d04d88320f0'; + +-- PPPO Base Miami - USCG +UPDATE transportation_offices SET provides_ppm_closeout = true WHERE id = '1b3e7496-efa7-48aa-ba22-b630d6fea98b'; + +-- JPPSO - North West (JEAT) - USA +UPDATE transportation_offices SET provides_ppm_closeout = false WHERE id = '5a3388e1-6d46-4639-ac8f-a8937dc26938'; + +-- PPPO NSWC Panama City Division - USN +UPDATE transportation_offices SET provides_ppm_closeout = true WHERE id = '57cf1e81-8113-4a52-bc50-3cb8902c2efd'; + +-- JPPSO - Mid Atlantic (BGAC) - USA +UPDATE transportation_offices SET provides_ppm_closeout = false WHERE id = '8e25ccc1-7891-4146-a9d0-cd0d48b59a50'; + +-- JPPSO - South West (LKNQ) - USN +UPDATE transportation_offices SET provides_ppm_closeout = false WHERE id = '27002d34-e9ea-4ef5-a086-f23d07c4088c'; \ No newline at end of file diff --git a/pkg/gen/ghcapi/ghcoperations/move/move_canceler.go b/pkg/gen/ghcapi/ghcoperations/move/move_canceler.go new file mode 100644 index 00000000000..5036a38e3a0 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/move/move_canceler.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package move + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// MoveCancelerHandlerFunc turns a function with the right signature into a move canceler handler +type MoveCancelerHandlerFunc func(MoveCancelerParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn MoveCancelerHandlerFunc) Handle(params MoveCancelerParams) middleware.Responder { + return fn(params) +} + +// MoveCancelerHandler interface for that can handle valid move canceler params +type MoveCancelerHandler interface { + Handle(MoveCancelerParams) middleware.Responder +} + +// NewMoveCanceler creates a new http.Handler for the move canceler operation +func NewMoveCanceler(ctx *middleware.Context, handler MoveCancelerHandler) *MoveCanceler { + return &MoveCanceler{Context: ctx, Handler: handler} +} + +/* + MoveCanceler swagger:route POST /moves/{moveID}/cancel move moveCanceler + +# Cancels a move + +cancels a move +*/ +type MoveCanceler struct { + Context *middleware.Context + Handler MoveCancelerHandler +} + +func (o *MoveCanceler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewMoveCancelerParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/move/move_canceler_parameters.go b/pkg/gen/ghcapi/ghcoperations/move/move_canceler_parameters.go new file mode 100644 index 00000000000..e997f36bc19 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/move/move_canceler_parameters.go @@ -0,0 +1,91 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package move + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// NewMoveCancelerParams creates a new MoveCancelerParams object +// +// There are no default values defined in the spec. +func NewMoveCancelerParams() MoveCancelerParams { + + return MoveCancelerParams{} +} + +// MoveCancelerParams contains all the bound params for the move canceler operation +// typically these are obtained from a http.Request +// +// swagger:parameters moveCanceler +type MoveCancelerParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*ID of the move + Required: true + In: path + */ + MoveID strfmt.UUID +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewMoveCancelerParams() beforehand. +func (o *MoveCancelerParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rMoveID, rhkMoveID, _ := route.Params.GetOK("moveID") + if err := o.bindMoveID(rMoveID, rhkMoveID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindMoveID binds and validates parameter MoveID from path. +func (o *MoveCancelerParams) bindMoveID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + // Format: uuid + value, err := formats.Parse("uuid", raw) + if err != nil { + return errors.InvalidType("moveID", "path", "strfmt.UUID", raw) + } + o.MoveID = *(value.(*strfmt.UUID)) + + if err := o.validateMoveID(formats); err != nil { + return err + } + + return nil +} + +// validateMoveID carries on validations for parameter MoveID +func (o *MoveCancelerParams) validateMoveID(formats strfmt.Registry) error { + + if err := validate.FormatOf("moveID", "path", "uuid", o.MoveID.String(), formats); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/move/move_canceler_responses.go b/pkg/gen/ghcapi/ghcoperations/move/move_canceler_responses.go new file mode 100644 index 00000000000..828a8086069 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/move/move_canceler_responses.go @@ -0,0 +1,329 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package move + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// MoveCancelerOKCode is the HTTP code returned for type MoveCancelerOK +const MoveCancelerOKCode int = 200 + +/* +MoveCancelerOK Successfully cancelled move + +swagger:response moveCancelerOK +*/ +type MoveCancelerOK struct { + + /* + In: Body + */ + Payload *ghcmessages.Move `json:"body,omitempty"` +} + +// NewMoveCancelerOK creates MoveCancelerOK with default headers values +func NewMoveCancelerOK() *MoveCancelerOK { + + return &MoveCancelerOK{} +} + +// WithPayload adds the payload to the move canceler o k response +func (o *MoveCancelerOK) WithPayload(payload *ghcmessages.Move) *MoveCancelerOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the move canceler o k response +func (o *MoveCancelerOK) SetPayload(payload *ghcmessages.Move) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *MoveCancelerOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// MoveCancelerForbiddenCode is the HTTP code returned for type MoveCancelerForbidden +const MoveCancelerForbiddenCode int = 403 + +/* +MoveCancelerForbidden The request was denied + +swagger:response moveCancelerForbidden +*/ +type MoveCancelerForbidden struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewMoveCancelerForbidden creates MoveCancelerForbidden with default headers values +func NewMoveCancelerForbidden() *MoveCancelerForbidden { + + return &MoveCancelerForbidden{} +} + +// WithPayload adds the payload to the move canceler forbidden response +func (o *MoveCancelerForbidden) WithPayload(payload *ghcmessages.Error) *MoveCancelerForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the move canceler forbidden response +func (o *MoveCancelerForbidden) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *MoveCancelerForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(403) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// MoveCancelerNotFoundCode is the HTTP code returned for type MoveCancelerNotFound +const MoveCancelerNotFoundCode int = 404 + +/* +MoveCancelerNotFound The requested resource wasn't found + +swagger:response moveCancelerNotFound +*/ +type MoveCancelerNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewMoveCancelerNotFound creates MoveCancelerNotFound with default headers values +func NewMoveCancelerNotFound() *MoveCancelerNotFound { + + return &MoveCancelerNotFound{} +} + +// WithPayload adds the payload to the move canceler not found response +func (o *MoveCancelerNotFound) WithPayload(payload *ghcmessages.Error) *MoveCancelerNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the move canceler not found response +func (o *MoveCancelerNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *MoveCancelerNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// MoveCancelerConflictCode is the HTTP code returned for type MoveCancelerConflict +const MoveCancelerConflictCode int = 409 + +/* +MoveCancelerConflict Conflict error + +swagger:response moveCancelerConflict +*/ +type MoveCancelerConflict struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewMoveCancelerConflict creates MoveCancelerConflict with default headers values +func NewMoveCancelerConflict() *MoveCancelerConflict { + + return &MoveCancelerConflict{} +} + +// WithPayload adds the payload to the move canceler conflict response +func (o *MoveCancelerConflict) WithPayload(payload *ghcmessages.Error) *MoveCancelerConflict { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the move canceler conflict response +func (o *MoveCancelerConflict) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *MoveCancelerConflict) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(409) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// MoveCancelerPreconditionFailedCode is the HTTP code returned for type MoveCancelerPreconditionFailed +const MoveCancelerPreconditionFailedCode int = 412 + +/* +MoveCancelerPreconditionFailed Precondition failed + +swagger:response moveCancelerPreconditionFailed +*/ +type MoveCancelerPreconditionFailed struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewMoveCancelerPreconditionFailed creates MoveCancelerPreconditionFailed with default headers values +func NewMoveCancelerPreconditionFailed() *MoveCancelerPreconditionFailed { + + return &MoveCancelerPreconditionFailed{} +} + +// WithPayload adds the payload to the move canceler precondition failed response +func (o *MoveCancelerPreconditionFailed) WithPayload(payload *ghcmessages.Error) *MoveCancelerPreconditionFailed { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the move canceler precondition failed response +func (o *MoveCancelerPreconditionFailed) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *MoveCancelerPreconditionFailed) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(412) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// MoveCancelerUnprocessableEntityCode is the HTTP code returned for type MoveCancelerUnprocessableEntity +const MoveCancelerUnprocessableEntityCode int = 422 + +/* +MoveCancelerUnprocessableEntity The payload was unprocessable. + +swagger:response moveCancelerUnprocessableEntity +*/ +type MoveCancelerUnprocessableEntity struct { + + /* + In: Body + */ + Payload *ghcmessages.ValidationError `json:"body,omitempty"` +} + +// NewMoveCancelerUnprocessableEntity creates MoveCancelerUnprocessableEntity with default headers values +func NewMoveCancelerUnprocessableEntity() *MoveCancelerUnprocessableEntity { + + return &MoveCancelerUnprocessableEntity{} +} + +// WithPayload adds the payload to the move canceler unprocessable entity response +func (o *MoveCancelerUnprocessableEntity) WithPayload(payload *ghcmessages.ValidationError) *MoveCancelerUnprocessableEntity { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the move canceler unprocessable entity response +func (o *MoveCancelerUnprocessableEntity) SetPayload(payload *ghcmessages.ValidationError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *MoveCancelerUnprocessableEntity) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(422) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// MoveCancelerInternalServerErrorCode is the HTTP code returned for type MoveCancelerInternalServerError +const MoveCancelerInternalServerErrorCode int = 500 + +/* +MoveCancelerInternalServerError A server error occurred + +swagger:response moveCancelerInternalServerError +*/ +type MoveCancelerInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewMoveCancelerInternalServerError creates MoveCancelerInternalServerError with default headers values +func NewMoveCancelerInternalServerError() *MoveCancelerInternalServerError { + + return &MoveCancelerInternalServerError{} +} + +// WithPayload adds the payload to the move canceler internal server error response +func (o *MoveCancelerInternalServerError) WithPayload(payload *ghcmessages.Error) *MoveCancelerInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the move canceler internal server error response +func (o *MoveCancelerInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *MoveCancelerInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/move/move_canceler_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/move/move_canceler_urlbuilder.go new file mode 100644 index 00000000000..f3f6ebb25eb --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/move/move_canceler_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package move + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/strfmt" +) + +// MoveCancelerURL generates an URL for the move canceler operation +type MoveCancelerURL struct { + MoveID strfmt.UUID + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *MoveCancelerURL) WithBasePath(bp string) *MoveCancelerURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *MoveCancelerURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *MoveCancelerURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/moves/{moveID}/cancel" + + moveID := o.MoveID.String() + if moveID != "" { + _path = strings.Replace(_path, "{moveID}", moveID, -1) + } else { + return nil, errors.New("moveId is required on MoveCancelerURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *MoveCancelerURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *MoveCancelerURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *MoveCancelerURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on MoveCancelerURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on MoveCancelerURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *MoveCancelerURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go new file mode 100644 index 00000000000..78f7901ab05 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// BulkDownloadHandlerFunc turns a function with the right signature into a bulk download handler +type BulkDownloadHandlerFunc func(BulkDownloadParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn BulkDownloadHandlerFunc) Handle(params BulkDownloadParams) middleware.Responder { + return fn(params) +} + +// BulkDownloadHandler interface for that can handle valid bulk download params +type BulkDownloadHandler interface { + Handle(BulkDownloadParams) middleware.Responder +} + +// NewBulkDownload creates a new http.Handler for the bulk download operation +func NewBulkDownload(ctx *middleware.Context, handler BulkDownloadHandler) *BulkDownload { + return &BulkDownload{Context: ctx, Handler: handler} +} + +/* + BulkDownload swagger:route GET /payment-requests/{paymentRequestID}/bulkDownload paymentRequests bulkDownload + +# Downloads all Payment Request documents as a PDF + +This endpoint downloads all uploaded payment request documentation combined into a single PDF. +*/ +type BulkDownload struct { + Context *middleware.Context + Handler BulkDownloadHandler +} + +func (o *BulkDownload) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewBulkDownloadParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go new file mode 100644 index 00000000000..ff67f05c88a --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" +) + +// NewBulkDownloadParams creates a new BulkDownloadParams object +// +// There are no default values defined in the spec. +func NewBulkDownloadParams() BulkDownloadParams { + + return BulkDownloadParams{} +} + +// BulkDownloadParams contains all the bound params for the bulk download operation +// typically these are obtained from a http.Request +// +// swagger:parameters bulkDownload +type BulkDownloadParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*the id for the payment-request with files to be downloaded + Required: true + In: path + */ + PaymentRequestID string +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewBulkDownloadParams() beforehand. +func (o *BulkDownloadParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rPaymentRequestID, rhkPaymentRequestID, _ := route.Params.GetOK("paymentRequestID") + if err := o.bindPaymentRequestID(rPaymentRequestID, rhkPaymentRequestID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindPaymentRequestID binds and validates parameter PaymentRequestID from path. +func (o *BulkDownloadParams) bindPaymentRequestID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.PaymentRequestID = raw + + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go new file mode 100644 index 00000000000..b00eca8e5c1 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go @@ -0,0 +1,170 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// BulkDownloadOKCode is the HTTP code returned for type BulkDownloadOK +const BulkDownloadOKCode int = 200 + +/* +BulkDownloadOK Payment Request Files PDF + +swagger:response bulkDownloadOK +*/ +type BulkDownloadOK struct { + /*File name to download + + */ + ContentDisposition string `json:"Content-Disposition"` + + /* + In: Body + */ + Payload io.ReadCloser `json:"body,omitempty"` +} + +// NewBulkDownloadOK creates BulkDownloadOK with default headers values +func NewBulkDownloadOK() *BulkDownloadOK { + + return &BulkDownloadOK{} +} + +// WithContentDisposition adds the contentDisposition to the bulk download o k response +func (o *BulkDownloadOK) WithContentDisposition(contentDisposition string) *BulkDownloadOK { + o.ContentDisposition = contentDisposition + return o +} + +// SetContentDisposition sets the contentDisposition to the bulk download o k response +func (o *BulkDownloadOK) SetContentDisposition(contentDisposition string) { + o.ContentDisposition = contentDisposition +} + +// WithPayload adds the payload to the bulk download o k response +func (o *BulkDownloadOK) WithPayload(payload io.ReadCloser) *BulkDownloadOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download o k response +func (o *BulkDownloadOK) SetPayload(payload io.ReadCloser) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + // response header Content-Disposition + + contentDisposition := o.ContentDisposition + if contentDisposition != "" { + rw.Header().Set("Content-Disposition", contentDisposition) + } + + rw.WriteHeader(200) + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// BulkDownloadBadRequestCode is the HTTP code returned for type BulkDownloadBadRequest +const BulkDownloadBadRequestCode int = 400 + +/* +BulkDownloadBadRequest The request payload is invalid + +swagger:response bulkDownloadBadRequest +*/ +type BulkDownloadBadRequest struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewBulkDownloadBadRequest creates BulkDownloadBadRequest with default headers values +func NewBulkDownloadBadRequest() *BulkDownloadBadRequest { + + return &BulkDownloadBadRequest{} +} + +// WithPayload adds the payload to the bulk download bad request response +func (o *BulkDownloadBadRequest) WithPayload(payload *ghcmessages.Error) *BulkDownloadBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download bad request response +func (o *BulkDownloadBadRequest) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// BulkDownloadInternalServerErrorCode is the HTTP code returned for type BulkDownloadInternalServerError +const BulkDownloadInternalServerErrorCode int = 500 + +/* +BulkDownloadInternalServerError A server error occurred + +swagger:response bulkDownloadInternalServerError +*/ +type BulkDownloadInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewBulkDownloadInternalServerError creates BulkDownloadInternalServerError with default headers values +func NewBulkDownloadInternalServerError() *BulkDownloadInternalServerError { + + return &BulkDownloadInternalServerError{} +} + +// WithPayload adds the payload to the bulk download internal server error response +func (o *BulkDownloadInternalServerError) WithPayload(payload *ghcmessages.Error) *BulkDownloadInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download internal server error response +func (o *BulkDownloadInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go new file mode 100644 index 00000000000..7f67d894191 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go @@ -0,0 +1,99 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" +) + +// BulkDownloadURL generates an URL for the bulk download operation +type BulkDownloadURL struct { + PaymentRequestID string + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *BulkDownloadURL) WithBasePath(bp string) *BulkDownloadURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *BulkDownloadURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *BulkDownloadURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/payment-requests/{paymentRequestID}/bulkDownload" + + paymentRequestID := o.PaymentRequestID + if paymentRequestID != "" { + _path = strings.Replace(_path, "{paymentRequestID}", paymentRequestID, -1) + } else { + return nil, errors.New("paymentRequestId is required on BulkDownloadURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *BulkDownloadURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *BulkDownloadURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *BulkDownloadURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on BulkDownloadURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on BulkDownloadURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *BulkDownloadURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/payment_request/service_param_value_lookups/locked_price_cents_lookup.go b/pkg/payment_request/service_param_value_lookups/locked_price_cents_lookup.go new file mode 100644 index 00000000000..f0a5dc62c49 --- /dev/null +++ b/pkg/payment_request/service_param_value_lookups/locked_price_cents_lookup.go @@ -0,0 +1,21 @@ +package serviceparamvaluelookups + +import ( + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/models" +) + +// LockedPriceCents does lookup on serviceItem +type LockedPriceCentsLookup struct { + ServiceItem models.MTOServiceItem +} + +func (r LockedPriceCentsLookup) lookup(appCtx appcontext.AppContext, _ *ServiceItemParamKeyData) (string, error) { + lockedPriceCents := r.ServiceItem.LockedPriceCents + if lockedPriceCents == nil { + return "0", apperror.NewConflictError(r.ServiceItem.ID, "unable to find locked price cents") + } + + return lockedPriceCents.ToMillicents().ToCents().String(), nil +} diff --git a/pkg/services/move/move_canceler.go b/pkg/services/move/move_canceler.go new file mode 100644 index 00000000000..26a0c2e04bb --- /dev/null +++ b/pkg/services/move/move_canceler.go @@ -0,0 +1,88 @@ +package move + +import ( + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" +) + +type moveCanceler struct{} + +func NewMoveCanceler() services.MoveCanceler { + return &moveCanceler{} +} + +func (f *moveCanceler) CancelMove(appCtx appcontext.AppContext, moveID uuid.UUID) (*models.Move, error) { + move := &models.Move{} + err := appCtx.DB().Find(move, moveID) + if err != nil { + return nil, apperror.NewNotFoundError(moveID, "while looking for a move") + } + + moveDelta := move + moveDelta.Status = models.MoveStatusCANCELED + + // get all shipments in move for cancellation + var shipments []models.MTOShipment + err = appCtx.DB().EagerPreload("Status", "PPMShipment", "PPMShipment.Status").Where("mto_shipments.move_id = $1", move.ID).All(&shipments) + if err != nil { + return nil, apperror.NewNotFoundError(moveID, "while looking for shipments") + } + + txnErr := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { + for _, shipment := range shipments { + shipmentDelta := shipment + shipmentDelta.Status = models.MTOShipmentStatusCanceled + + if shipment.PPMShipment != nil { + if shipment.PPMShipment.Status == models.PPMShipmentStatusCloseoutComplete { + return apperror.NewConflictError(move.ID, " cannot cancel move with approved shipment.") + } + var ppmshipment models.PPMShipment + qerr := appCtx.DB().Where("id = ?", shipment.PPMShipment.ID).First(&ppmshipment) + if qerr != nil { + return apperror.NewNotFoundError(ppmshipment.ID, "while looking for ppm shipment") + } + + ppmshipment.Status = models.PPMShipmentStatusCanceled + + verrs, err := txnAppCtx.DB().ValidateAndUpdate(&ppmshipment) + if verrs != nil && verrs.HasAny() { + return apperror.NewInvalidInputError(shipment.ID, err, verrs, "Validation errors found while setting shipment status") + } else if err != nil { + return apperror.NewQueryError("PPM Shipment", err, "Failed to update status for ppm shipment") + } + } + + if shipment.Status != models.MTOShipmentStatusApproved { + verrs, err := txnAppCtx.DB().ValidateAndUpdate(&shipmentDelta) + if verrs != nil && verrs.HasAny() { + return apperror.NewInvalidInputError(shipment.ID, err, verrs, "Validation errors found while setting shipment status") + } else if err != nil { + return apperror.NewQueryError("Shipment", err, "Failed to update status for shipment") + } + } else { + return apperror.NewConflictError(move.ID, " cannot cancel move with approved shipment.") + } + } + + verrs, err := txnAppCtx.DB().ValidateAndUpdate(moveDelta) + if verrs != nil && verrs.HasAny() { + return apperror.NewInvalidInputError( + move.ID, err, verrs, "Validation errors found while setting move status") + } else if err != nil { + return apperror.NewQueryError("Move", err, "Failed to update status for move") + } + + return nil + }) + + if txnErr != nil { + return nil, txnErr + } + + return move, nil +} diff --git a/pkg/services/move/move_canceler_test.go b/pkg/services/move/move_canceler_test.go new file mode 100644 index 00000000000..314b0719075 --- /dev/null +++ b/pkg/services/move/move_canceler_test.go @@ -0,0 +1,33 @@ +package move + +import ( + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" +) + +func (suite *MoveServiceSuite) TestMoveCanceler() { + moveCanceler := NewMoveCanceler() + + suite.Run("successfully cancels a move", func() { + move := factory.BuildMove(suite.DB(), nil, nil) + + suite.NotEqual(move.Status, models.MoveStatusCANCELED) + + m, err := moveCanceler.CancelMove(suite.AppContextForTest(), move.ID) + suite.NoError(err) + suite.Equal(m.Status, models.MoveStatusCANCELED) + }) + + suite.Run("fails to cancel move with approved hhg shipment", func() { + move := factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusApproved, + }, + }, + }, nil) + + _, err := moveCanceler.CancelMove(suite.AppContextForTest(), move.ID) + suite.Error(err) + }) +} diff --git a/pkg/services/payment_request/payment_request_bulk_download_creator.go b/pkg/services/payment_request/payment_request_bulk_download_creator.go new file mode 100644 index 00000000000..1178f4c991c --- /dev/null +++ b/pkg/services/payment_request/payment_request_bulk_download_creator.go @@ -0,0 +1,57 @@ +package paymentrequest + +import ( + "fmt" + + "github.com/gofrs/uuid" + "github.com/spf13/afero" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/paperwork" + "github.com/transcom/mymove/pkg/services" +) + +type paymentRequestBulkDownloadCreator struct { + pdfGenerator *paperwork.Generator +} + +func NewPaymentRequestBulkDownloadCreator(pdfGenerator *paperwork.Generator) services.PaymentRequestBulkDownloadCreator { + return &paymentRequestBulkDownloadCreator{ + pdfGenerator, + } +} + +func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestBulkDownload(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) { + errMsgPrefix := "error creating Payment Request packet" + + paymentRequest := models.PaymentRequest{} + err := appCtx.DB().Q().Eager( + "MoveTaskOrder", + "ProofOfServiceDocs", + "ProofOfServiceDocs.PrimeUploads", + "ProofOfServiceDocs.PrimeUploads.Upload", + ).Find(&paymentRequest, paymentRequestID) + if err != nil || len(paymentRequest.ProofOfServiceDocs) < 1 { + return nil, fmt.Errorf("%s: %w", errMsgPrefix, err) + } + + var primeUploads models.Uploads + for _, serviceDoc := range paymentRequest.ProofOfServiceDocs { + for _, upload := range serviceDoc.PrimeUploads { + primeUploads = append(primeUploads, upload.Upload) + } + } + + pdfs, err := p.pdfGenerator.ConvertUploadsToPDF(appCtx, primeUploads, false) + if err != nil { + return nil, fmt.Errorf("%s error generating pdf", err) + } + + pdfFile, err := p.pdfGenerator.MergePDFFiles(appCtx, pdfs) + if err != nil { + return nil, fmt.Errorf("%s error generating merged pdf", err) + } + + return pdfFile, nil +} From 475961ade470733b5381b5d234613e5106b5ef89 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Tue, 24 Sep 2024 02:02:18 +0000 Subject: [PATCH 16/28] 20529 Merge from main 2 --- .circleci/config.yml | 4 +- Dockerfile.e2e | 2 +- Dockerfile.migrations | 2 +- Dockerfile.migrations_local | 2 +- Dockerfile.reviewapp | 2 +- Dockerfile.tools | 2 +- Dockerfile.tools_local | 2 +- README.md | 3 +- migrations/app/migrations_manifest.txt | 5 + package.json | 2 +- pkg/factory/mto_service_item_factory.go | 16 +- pkg/gen/ghcapi/configure_mymove.go | 10 + pkg/gen/ghcapi/embedded_spec.go | 274 +++++++++++++++- pkg/gen/ghcapi/ghcoperations/mymove_api.go | 24 ++ ...et_services_counseling_queue_parameters.go | 29 +- ...et_services_counseling_queue_urlbuilder.go | 9 + .../get_transportation_offices.go | 4 +- .../ghcmessages/create_customer_payload.go | 21 +- pkg/gen/ghcmessages/customer.go | 6 +- pkg/gen/ghcmessages/p_p_m_shipment_status.go | 5 +- pkg/gen/ghcmessages/queue_move.go | 3 + .../ghcmessages/service_item_param_name.go | 5 +- pkg/gen/internalapi/embedded_spec.go | 18 +- .../create_service_member_payload.go | 4 +- .../internalmessages/p_p_m_shipment_status.go | 5 +- .../patch_service_member_payload.go | 4 +- .../service_member_payload.go | 4 +- pkg/gen/primeapi/embedded_spec.go | 12 +- .../primemessages/p_p_m_shipment_status.go | 5 +- .../primemessages/service_item_param_name.go | 5 +- pkg/gen/primev2api/embedded_spec.go | 12 +- .../primev2messages/p_p_m_shipment_status.go | 5 +- .../service_item_param_name.go | 5 +- pkg/gen/primev3api/embedded_spec.go | 168 ++++++++-- .../m_t_o_shipment_without_service_items.go | 168 +++++++++- pkg/gen/primev3messages/p_p_m_shipment.go | 108 +++++++ .../primev3messages/p_p_m_shipment_status.go | 5 +- .../service_item_param_name.go | 5 +- .../primev3messages/update_m_t_o_shipment.go | 52 ++++ .../primev3messages/update_p_p_m_shipment.go | 60 ++++ pkg/handlers/ghcapi/api.go | 11 + pkg/handlers/ghcapi/customer.go | 51 ++- pkg/handlers/ghcapi/customer_test.go | 81 ++++- .../internal/payloads/model_to_payload.go | 8 +- pkg/handlers/ghcapi/move.go | 101 ++++-- pkg/handlers/ghcapi/mto_service_items.go | 6 +- pkg/handlers/ghcapi/orders.go | 24 +- pkg/handlers/ghcapi/orders_test.go | 13 +- pkg/handlers/ghcapi/payment_request.go | 37 +++ pkg/handlers/ghcapi/queues.go | 1 + pkg/handlers/ghcapi/queues_test.go | 4 +- pkg/handlers/ghcapi/tranportation_offices.go | 5 +- pkg/handlers/internalapi/mto_shipment_test.go | 4 +- pkg/handlers/internalapi/office_test.go | 4 +- pkg/handlers/primeapi/mto_shipment_address.go | 14 +- .../primeapi/mto_shipment_address_test.go | 48 +++ .../primeapiv3/move_task_order_test.go | 18 +- .../primeapiv3/payloads/model_to_payload.go | 18 +- .../primeapiv3/payloads/payload_to_model.go | 34 ++ pkg/models/move.go | 12 +- pkg/models/move_test.go | 21 +- pkg/models/ppm_shipment.go | 8 +- pkg/models/queue_test.go | 4 +- pkg/models/service_item_param_key.go | 4 + pkg/paperwork/generator.go | 144 ++++++++- pkg/paperwork/generator_test.go | 61 +++- .../service_param_value_lookups.go | 5 + pkg/services/ghc_rate_engine.go | 4 +- .../counseling_services_pricer.go | 25 +- .../counseling_services_pricer_test.go | 27 +- .../management_services_pricer.go | 23 +- .../management_services_pricer_test.go | 27 +- .../ghcrateengine/service_item_pricer_test.go | 11 +- pkg/services/invoice/process_edi997.go | 3 + pkg/services/invoice/process_edi997_test.go | 4 + .../mocks/CounselingServicesPricer.go | 24 +- .../mocks/ManagementServicesPricer.go | 24 +- pkg/services/move.go | 3 + pkg/services/move/move_router_test.go | 2 +- .../move_task_order_fetcher.go | 2 + .../move_task_order_fetcher_test.go | 42 +++ .../mto_shipment_address_updater.go | 2 + .../mto_shipment/mto_shipment_creator.go | 13 +- .../mto_shipment/mto_shipment_creator_test.go | 39 ++- .../mto_shipment/mto_shipment_updater.go | 17 +- pkg/services/order.go | 3 +- pkg/services/order/order_fetcher.go | 18 +- pkg/services/order/order_fetcher_test.go | 43 +-- pkg/services/payment_request.go | 5 + .../payment_request_recalculator_test.go | 30 +- .../shipment_address_update_requester.go | 2 +- .../primeSimulatorFlows.spec.js | 2 +- playwright/tests/utils/waitForPage.js | 23 +- scripts/run-e2e-test | 2 +- .../CustomerHeader/CustomerHeader.test.jsx | 8 +- src/components/CustomerHeader/index.jsx | 4 +- .../DocumentViewer/DocumentViewer.jsx | 20 +- .../DocumentViewer/DocumentViewer.module.scss | 7 + .../DocumentViewer/DocumentViewer.test.jsx | 42 ++- .../DefinitionLists/CustomerInfoList.jsx | 4 +- .../DefinitionLists/CustomerInfoList.test.jsx | 2 +- .../PaymentRequestCard/PaymentRequestCard.jsx | 78 ++--- .../PaymentRequestCard.test.jsx | 23 +- .../ServicesCounselingTabNav.jsx | 12 +- .../ServicesCounselingTabNav.test.jsx | 6 +- src/components/Office/TXOTabNav/TXOTabNav.jsx | 4 + .../Office/TXOTabNav/TXOTabNav.test.jsx | 3 +- src/components/PrimeUI/Shipment/Shipment.jsx | 74 ++++- .../PrimeUI/Shipment/Shipment.test.jsx | 44 ++- src/components/Table/SearchResultsTable.jsx | 9 +- src/components/Table/TableCSVExportButton.jsx | 26 +- .../Table/TableCSVExportButton.test.jsx | 26 ++ src/components/Table/TableQueue.jsx | 9 +- src/constants/queues.js | 9 - src/pages/MyMove/Profile/ContactInfo.jsx | 6 +- src/pages/MyMove/Profile/EditContactInfo.jsx | 4 +- .../CustomerOnboarding/CreateCustomerForm.jsx | 83 ++++- .../CreateCustomerForm.test.jsx | 182 ++++++++++- .../HeadquartersQueues/HeadquartersQueues.jsx | 4 + src/pages/Office/MoveDetails/MoveDetails.jsx | 39 ++- .../Office/MoveDetails/MoveDetails.test.jsx | 26 ++ src/pages/Office/MoveQueue/MoveQueue.jsx | 2 +- src/pages/Office/MoveQueue/MoveQueue.test.jsx | 220 ++++++++----- .../PaymentRequestReview.jsx | 6 +- .../PaymentRequestReview.test.jsx | 1 + ...icesCounselingEditShipmentDetails.test.jsx | 2 +- .../ServicesCounselingMoveDetails.jsx | 44 ++- .../ServicesCounselingMoveDetails.test.jsx | 104 ++++++- .../ServicesCounselingMoveInfo.jsx | 4 + .../ServicesCounselingQueue.jsx | 293 +++++++++--------- .../ServicesCounselingQueue.test.jsx | 2 + src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx | 4 + .../Shipment/PrimeUIShipmentUpdate.jsx | 68 +++- .../Shipment/PrimeUIShipmentUpdateAddress.jsx | 100 +++--- .../PrimeUIShipmentUpdateAddress.test.jsx | 116 +------ .../PrimeUIShipmentUpdateAddressForm.jsx | 9 +- .../Shipment/PrimeUIShipmentUpdateForm.jsx | 32 ++ .../Shipment/PrimeUIShipmentUpdatePPMForm.jsx | 81 ++++- .../PrimeUIShipmentUpdatePPMForm.test.jsx | 78 ++++- src/services/ghcApi.js | 4 + src/shared/constants.js | 20 ++ src/shared/styles/colors.scss | 6 +- src/utils/customer.js | 25 ++ src/utils/formatters.js | 13 + .../definitions/PPMShipmentStatus.yaml | 1 + .../definitions/ServiceItemParamName.yaml | 1 + .../v3/MTOShipmentWithoutServiceItems.yaml | 12 +- .../definitions/prime/v3/PPMShipment.yaml | 12 + swagger-def/ghc.yaml | 85 ++++- swagger-def/internal.yaml | 6 +- swagger-def/prime_v3.yaml | 26 ++ swagger/ghc.yaml | 92 +++++- swagger/internal.yaml | 7 +- swagger/prime.yaml | 2 + swagger/prime_v2.yaml | 2 + swagger/prime_v3.yaml | 62 +++- 156 files changed, 3510 insertions(+), 1024 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 775872e9ec1..b0f66811d27 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1554,7 +1554,7 @@ jobs: # Use the BuildNum to update the cache key so that the # coverage cache is always updated - save_cache: - key: v7-server-tests-coverage-{{ .BuildNum }} + key: v8-server-tests-coverage-{{ .BuildNum }} paths: - ~/transcom/mymove/tmp/baseline-go-coverage when: always @@ -1684,7 +1684,7 @@ jobs: # Use the BuildNum to update the cache key so that the # coverage cache is always updated - save_cache: - key: v5-client-tests-coverage-{{ .BuildNum }} + key: v6-client-tests-coverage-{{ .BuildNum }} paths: - ~/transcom/mymove/tmp/baseline-jest-coverage when: always diff --git a/Dockerfile.e2e b/Dockerfile.e2e index d161f38e33d..c88d243eb48 100644 --- a/Dockerfile.e2e +++ b/Dockerfile.e2e @@ -1,4 +1,4 @@ -FROM alpine:3.20.2 +FROM alpine:3.20.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.migrations b/Dockerfile.migrations index c3852e777b4..b4187431cd2 100644 --- a/Dockerfile.migrations +++ b/Dockerfile.migrations @@ -1,4 +1,4 @@ -FROM alpine:3.20.2 +FROM alpine:3.20.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.migrations_local b/Dockerfile.migrations_local index de2cbbb3481..3b2d11e7444 100644 --- a/Dockerfile.migrations_local +++ b/Dockerfile.migrations_local @@ -18,7 +18,7 @@ RUN rm -f bin/milmove && make bin/milmove # FINAL # ######### -FROM alpine:3.20.2 +FROM alpine:3.20.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.reviewapp b/Dockerfile.reviewapp index c27b975a8c9..cd35976410f 100644 --- a/Dockerfile.reviewapp +++ b/Dockerfile.reviewapp @@ -45,7 +45,7 @@ RUN set -x \ && make bin/generate-test-data # define migrations before client build since it doesn't need client -FROM alpine:3.20.2 as migrate +FROM alpine:3.20.3 as migrate COPY --from=server_builder /build/bin/rds-ca-2019-root.pem /bin/rds-ca-2019-root.pem COPY --from=server_builder /build/bin/milmove /bin/milmove diff --git a/Dockerfile.tools b/Dockerfile.tools index c3fd78134b6..97fe065b9d3 100644 --- a/Dockerfile.tools +++ b/Dockerfile.tools @@ -1,4 +1,4 @@ -FROM alpine:3.20.2 +FROM alpine:3.20.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/Dockerfile.tools_local b/Dockerfile.tools_local index a68970c839d..fa2f95af5f7 100644 --- a/Dockerfile.tools_local +++ b/Dockerfile.tools_local @@ -18,7 +18,7 @@ RUN rm -f bin/prime-api-client && make bin/prime-api-client # FINAL # ######### -FROM alpine:3.20.2 +FROM alpine:3.20.3 # hadolint ignore=DL3017 RUN apk upgrade --no-cache busybox diff --git a/README.md b/README.md index b80b7b38a2c..78e795bb938 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,8 @@ [![GoDoc](https://godoc.org/github.com/transcom/mymove?status.svg)](https://godoc.org/github.com/transcom/mymove) -This repository contains the application source code for the Personal Property Prototype, a possible next generation version of the Defense Personal Property System (DPS). DPS is an online system managed by the U.S. [Department of Defense](https://www.defense.gov/) (DoD) [Transportation Command](http://www.ustranscom.mil/) (USTRANSCOM) and is used by service members and their families to manage household goods moves. +This repository contains the application source code for the MilMove application, the next generation version of the Defense Personal Property System (DPS). DPS is an online system managed by the U.S. [Department of Defense](https://www.defense.gov/) (DoD) [Transportation Command](http://www.ustranscom.mil/) (USTRANSCOM) and is used by service members and their families to manage household goods moves. -This prototype was built by a [Defense Digital Service](https://www.dds.mil/) team in support of USTRANSCOM's mission. ## License Information diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index ab0cc2e13f5..b3215527673 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -973,6 +973,7 @@ 20240725190050_update_payment_request_status_tpps_received.up.sql 20240729162353_joseph_doye_cn_cac.up.sql 20240729164930_mai_do_cac.up.sql +20240729185147_add_cancel_to_ppm_status_enum.up.sql 20240729200347_add_mto_approved_at_timestamp.up.sql 20240730161630_remove-boat-shipments-index.up.sql 20240731125005_retroactively_update_approve_at_column_based_on_available_to_prime.up.sql @@ -993,3 +994,7 @@ 20240819164156_update_pws_violations_pt3.up.sql 20240820125856_allow_pptas_migration.up.sql 20240820151043_add_gsr_role.up.sql +20240822180409_adding_locked_price_cents_service_param.up.sql +20240909194514_pricing_unpriced_ms_cs_service_items.up.sql +20240910021542_populating_locked_price_cents_from_pricing_estimate_for_ms_and_cs_service_items.up.sql +20240917132411_update_provides_ppm_closeout_transportation_offices.up.sql diff --git a/package.json b/package.json index 8670451fad2..117476ac088 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "test": "react-app-rewired test --env=jsdom", "test:debug": "react-app-rewired --inspect-brk test --runInBand --env=jsdom", "test:coverage": "react-app-rewired test --coverage --reporters=default --reporters=jest-junit --env=jsdom --coverageDirectory=coverage --maxWorkers=4 --watchAll=false", - "test:e2e": "playwright test --trace=on", + "test:e2e": "A11Y_AUDIT=true playwright test --trace=on", "prettier": "prettier --write --loglevel warn 'src/**/*.{js,jsx}' playwright/tests/**/*.js'", "prettier-ci": "prettier --check 'src/**/*.{js,jsx}' playwright/tests/**/*.js", "lint": "eslint --ext .js,.jsx --max-warnings=0 src playwright/tests", diff --git a/pkg/factory/mto_service_item_factory.go b/pkg/factory/mto_service_item_factory.go index 2ec6548b02b..c39d7680d4b 100644 --- a/pkg/factory/mto_service_item_factory.go +++ b/pkg/factory/mto_service_item_factory.go @@ -9,6 +9,7 @@ import ( "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/testdatagen" + "github.com/transcom/mymove/pkg/unit" ) type mtoServiceItemBuildType byte @@ -56,6 +57,8 @@ func buildMTOServiceItemWithBuildType(db *pop.Connection, customs []Customizatio requestedApprovalsRequestedStatus := false + var lockedPriceCents = unit.Cents(12303) + // Create default MTOServiceItem mtoServiceItem := models.MTOServiceItem{ MoveTaskOrder: move, @@ -67,6 +70,7 @@ func buildMTOServiceItemWithBuildType(db *pop.Connection, customs []Customizatio Status: models.MTOServiceItemStatusSubmitted, RequestedApprovalsRequestedStatus: &requestedApprovalsRequestedStatus, CustomerExpense: isCustomerExpense, + LockedPriceCents: &lockedPriceCents, } // only set SITOriginHHGOriginalAddress if a customization is provided @@ -341,15 +345,23 @@ var ( Type: models.ServiceItemParamTypeString, Origin: models.ServiceItemParamOriginPrime, } + paramLockedPriceCents = models.ServiceItemParamKey{ + Key: models.ServiceItemParamNameLockedPriceCents, + Description: "locked price cents", + Type: models.ServiceItemParamTypeInteger, + Origin: models.ServiceItemParamOriginSystem, + } fixtureServiceItemParamsMap = map[models.ReServiceCode]models.ServiceItemParamKeys{ models.ReServiceCodeCS: { - paramContractCode, paramMTOAvailableAToPrimeAt, + paramContractCode, + paramLockedPriceCents, paramPriceRateOrFactor, }, models.ReServiceCodeMS: { - paramContractCode, paramMTOAvailableAToPrimeAt, + paramContractCode, + paramLockedPriceCents, paramPriceRateOrFactor, }, models.ReServiceCodeDLH: { diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 9aa1fcb4609..af39abd3a62 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -95,6 +95,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation report_violations.AssociateReportViolations has not yet been implemented") }) } + if api.PaymentRequestsBulkDownloadHandler == nil { + api.PaymentRequestsBulkDownloadHandler = payment_requests.BulkDownloadHandlerFunc(func(params payment_requests.BulkDownloadParams) middleware.Responder { + return middleware.NotImplemented("operation payment_requests.BulkDownload has not yet been implemented") + }) + } if api.OrderCounselingUpdateAllowanceHandler == nil { api.OrderCounselingUpdateAllowanceHandler = order.CounselingUpdateAllowanceHandlerFunc(func(params order.CounselingUpdateAllowanceParams) middleware.Responder { return middleware.NotImplemented("operation order.CounselingUpdateAllowance has not yet been implemented") @@ -370,6 +375,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation queues.ListPrimeMoves has not yet been implemented") }) } + if api.MoveMoveCancelerHandler == nil { + api.MoveMoveCancelerHandler = move.MoveCancelerHandlerFunc(func(params move.MoveCancelerParams) middleware.Responder { + return middleware.NotImplemented("operation move.MoveCanceler has not yet been implemented") + }) + } if api.ShipmentRejectShipmentHandler == nil { api.ShipmentRejectShipmentHandler = shipment.RejectShipmentHandlerFunc(func(params shipment.RejectShipmentParams) middleware.Responder { return middleware.NotImplemented("operation shipment.RejectShipment has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index b3478940332..86e8051966f 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -2335,6 +2335,58 @@ func init() { } ] }, + "/moves/{moveID}/cancel": { + "post": { + "description": "cancels a move", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "move" + ], + "summary": "Cancels a move", + "operationId": "moveCanceler", + "responses": { + "200": { + "description": "Successfully cancelled move", + "schema": { + "$ref": "#/definitions/Move" + } + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "409": { + "$ref": "#/responses/Conflict" + }, + "412": { + "$ref": "#/responses/PreconditionFailed" + }, + "422": { + "$ref": "#/responses/UnprocessableEntity" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + }, + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "ID of the move", + "name": "moveID", + "in": "path", + "required": true + } + ] + }, "/moves/{moveID}/counseling-evaluation-reports-list": { "get": { "description": "Returns counseling evaluation reports for the specified move that are visible to the current office user", @@ -3273,6 +3325,49 @@ func init() { } ] }, + "/payment-requests/{paymentRequestID}/bulkDownload": { + "get": { + "description": "This endpoint downloads all uploaded payment request documentation combined into a single PDF.\n", + "produces": [ + "application/pdf" + ], + "tags": [ + "paymentRequests" + ], + "summary": "Downloads all Payment Request documents as a PDF", + "operationId": "bulkDownload", + "responses": { + "200": { + "description": "Payment Request Files PDF", + "schema": { + "type": "file", + "format": "binary" + }, + "headers": { + "Content-Disposition": { + "type": "string", + "description": "File name to download" + } + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + }, + "parameters": [ + { + "type": "string", + "description": "the id for the payment-request with files to be downloaded", + "name": "paymentRequestID", + "in": "path", + "required": true + } + ] + }, "/payment-requests/{paymentRequestID}/shipments-payment-sit-balance": { "get": { "description": "Returns all shipment payment request SIT usage to support partial SIT invoicing", @@ -4050,6 +4145,7 @@ func init() { "closeoutInitiated", "closeoutLocation", "ppmStatus", + "counselingOffice", "assignedTo" ], "type": "string", @@ -4085,6 +4181,12 @@ func init() { "name": "lastName", "in": "query" }, + { + "type": "string", + "description": "filters using a counselingOffice name of the move", + "name": "counselingOffice", + "in": "query" + }, { "type": "string", "description": "filters to match the unique service member's DoD ID", @@ -5714,14 +5816,14 @@ func init() { }, "/transportation-offices": { "get": { - "description": "Returns the transportation offices matching the search query", + "description": "Returns the transportation offices matching the search query that is enabled for PPM closeout", "produces": [ "application/json" ], "tags": [ "transportationOffice" ], - "summary": "Returns the transportation offices matching the search query", + "summary": "Returns the transportation offices matching the search query that is enabled for PPM closeout", "operationId": "getTransportationOffices", "parameters": [ { @@ -6712,8 +6814,9 @@ func init() { }, "edipi": { "type": "string", - "x-nullable": true, - "example": "John" + "maxLength": 10, + "x-nullable": false, + "example": "1234567890" }, "emailIsPreferred": { "type": "boolean" @@ -7313,10 +7416,10 @@ func init() { "current_address": { "$ref": "#/definitions/Address" }, - "dodID": { + "eTag": { "type": "string" }, - "eTag": { + "edipi": { "type": "string" }, "email": { @@ -11120,7 +11223,8 @@ func init() { "WAITING_ON_CUSTOMER", "NEEDS_ADVANCE_APPROVAL", "NEEDS_CLOSEOUT", - "CLOSEOUT_COMPLETE" + "CLOSEOUT_COMPLETE", + "CANCELED" ], "readOnly": true }, @@ -11623,6 +11727,10 @@ func init() { "type": "string", "x-nullable": true }, + "counselingOffice": { + "type": "string", + "x-nullable": true + }, "customer": { "$ref": "#/definitions/Customer" }, @@ -12405,7 +12513,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -17007,6 +17116,76 @@ func init() { } ] }, + "/moves/{moveID}/cancel": { + "post": { + "description": "cancels a move", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "move" + ], + "summary": "Cancels a move", + "operationId": "moveCanceler", + "responses": { + "200": { + "description": "Successfully cancelled move", + "schema": { + "$ref": "#/definitions/Move" + } + }, + "403": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "The requested resource wasn't found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "409": { + "description": "Conflict error", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "412": { + "description": "Precondition failed", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "422": { + "description": "The payload was unprocessable.", + "schema": { + "$ref": "#/definitions/ValidationError" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + }, + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "ID of the move", + "name": "moveID", + "in": "path", + "required": true + } + ] + }, "/moves/{moveID}/counseling-evaluation-reports-list": { "get": { "description": "Returns counseling evaluation reports for the specified move that are visible to the current office user", @@ -18138,6 +18317,55 @@ func init() { } ] }, + "/payment-requests/{paymentRequestID}/bulkDownload": { + "get": { + "description": "This endpoint downloads all uploaded payment request documentation combined into a single PDF.\n", + "produces": [ + "application/pdf" + ], + "tags": [ + "paymentRequests" + ], + "summary": "Downloads all Payment Request documents as a PDF", + "operationId": "bulkDownload", + "responses": { + "200": { + "description": "Payment Request Files PDF", + "schema": { + "type": "file", + "format": "binary" + }, + "headers": { + "Content-Disposition": { + "type": "string", + "description": "File name to download" + } + } + }, + "400": { + "description": "The request payload is invalid", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + }, + "parameters": [ + { + "type": "string", + "description": "the id for the payment-request with files to be downloaded", + "name": "paymentRequestID", + "in": "path", + "required": true + } + ] + }, "/payment-requests/{paymentRequestID}/shipments-payment-sit-balance": { "get": { "description": "Returns all shipment payment request SIT usage to support partial SIT invoicing", @@ -19193,6 +19421,7 @@ func init() { "closeoutInitiated", "closeoutLocation", "ppmStatus", + "counselingOffice", "assignedTo" ], "type": "string", @@ -19228,6 +19457,12 @@ func init() { "name": "lastName", "in": "query" }, + { + "type": "string", + "description": "filters using a counselingOffice name of the move", + "name": "counselingOffice", + "in": "query" + }, { "type": "string", "description": "filters to match the unique service member's DoD ID", @@ -21193,14 +21428,14 @@ func init() { }, "/transportation-offices": { "get": { - "description": "Returns the transportation offices matching the search query", + "description": "Returns the transportation offices matching the search query that is enabled for PPM closeout", "produces": [ "application/json" ], "tags": [ "transportationOffice" ], - "summary": "Returns the transportation offices matching the search query", + "summary": "Returns the transportation offices matching the search query that is enabled for PPM closeout", "operationId": "getTransportationOffices", "parameters": [ { @@ -22241,8 +22476,9 @@ func init() { }, "edipi": { "type": "string", - "x-nullable": true, - "example": "John" + "maxLength": 10, + "x-nullable": false, + "example": "1234567890" }, "emailIsPreferred": { "type": "boolean" @@ -22842,10 +23078,10 @@ func init() { "current_address": { "$ref": "#/definitions/Address" }, - "dodID": { + "eTag": { "type": "string" }, - "eTag": { + "edipi": { "type": "string" }, "email": { @@ -26722,7 +26958,8 @@ func init() { "WAITING_ON_CUSTOMER", "NEEDS_ADVANCE_APPROVAL", "NEEDS_CLOSEOUT", - "CLOSEOUT_COMPLETE" + "CLOSEOUT_COMPLETE", + "CANCELED" ], "readOnly": true }, @@ -27227,6 +27464,10 @@ func init() { "type": "string", "x-nullable": true }, + "counselingOffice": { + "type": "string", + "x-nullable": true + }, "customer": { "$ref": "#/definitions/Customer" }, @@ -28059,7 +28300,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index 7c6f7c15d2f..bec29e262f6 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -84,6 +84,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { ReportViolationsAssociateReportViolationsHandler: report_violations.AssociateReportViolationsHandlerFunc(func(params report_violations.AssociateReportViolationsParams) middleware.Responder { return middleware.NotImplemented("operation report_violations.AssociateReportViolations has not yet been implemented") }), + PaymentRequestsBulkDownloadHandler: payment_requests.BulkDownloadHandlerFunc(func(params payment_requests.BulkDownloadParams) middleware.Responder { + return middleware.NotImplemented("operation payment_requests.BulkDownload has not yet been implemented") + }), OrderCounselingUpdateAllowanceHandler: order.CounselingUpdateAllowanceHandlerFunc(func(params order.CounselingUpdateAllowanceParams) middleware.Responder { return middleware.NotImplemented("operation order.CounselingUpdateAllowance has not yet been implemented") }), @@ -249,6 +252,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { QueuesListPrimeMovesHandler: queues.ListPrimeMovesHandlerFunc(func(params queues.ListPrimeMovesParams) middleware.Responder { return middleware.NotImplemented("operation queues.ListPrimeMoves has not yet been implemented") }), + MoveMoveCancelerHandler: move.MoveCancelerHandlerFunc(func(params move.MoveCancelerParams) middleware.Responder { + return middleware.NotImplemented("operation move.MoveCanceler has not yet been implemented") + }), ShipmentRejectShipmentHandler: shipment.RejectShipmentHandlerFunc(func(params shipment.RejectShipmentParams) middleware.Responder { return middleware.NotImplemented("operation shipment.RejectShipment has not yet been implemented") }), @@ -422,6 +428,8 @@ type MymoveAPI struct { ShipmentApproveShipmentDiversionHandler shipment.ApproveShipmentDiversionHandler // ReportViolationsAssociateReportViolationsHandler sets the operation handler for the associate report violations operation ReportViolationsAssociateReportViolationsHandler report_violations.AssociateReportViolationsHandler + // PaymentRequestsBulkDownloadHandler sets the operation handler for the bulk download operation + PaymentRequestsBulkDownloadHandler payment_requests.BulkDownloadHandler // OrderCounselingUpdateAllowanceHandler sets the operation handler for the counseling update allowance operation OrderCounselingUpdateAllowanceHandler order.CounselingUpdateAllowanceHandler // OrderCounselingUpdateOrderHandler sets the operation handler for the counseling update order operation @@ -532,6 +540,8 @@ type MymoveAPI struct { MtoShipmentListMTOShipmentsHandler mto_shipment.ListMTOShipmentsHandler // QueuesListPrimeMovesHandler sets the operation handler for the list prime moves operation QueuesListPrimeMovesHandler queues.ListPrimeMovesHandler + // MoveMoveCancelerHandler sets the operation handler for the move canceler operation + MoveMoveCancelerHandler move.MoveCancelerHandler // ShipmentRejectShipmentHandler sets the operation handler for the reject shipment operation ShipmentRejectShipmentHandler shipment.RejectShipmentHandler // LinesOfAccountingRequestLineOfAccountingHandler sets the operation handler for the request line of accounting operation @@ -708,6 +718,9 @@ func (o *MymoveAPI) Validate() error { if o.ReportViolationsAssociateReportViolationsHandler == nil { unregistered = append(unregistered, "report_violations.AssociateReportViolationsHandler") } + if o.PaymentRequestsBulkDownloadHandler == nil { + unregistered = append(unregistered, "payment_requests.BulkDownloadHandler") + } if o.OrderCounselingUpdateAllowanceHandler == nil { unregistered = append(unregistered, "order.CounselingUpdateAllowanceHandler") } @@ -873,6 +886,9 @@ func (o *MymoveAPI) Validate() error { if o.QueuesListPrimeMovesHandler == nil { unregistered = append(unregistered, "queues.ListPrimeMovesHandler") } + if o.MoveMoveCancelerHandler == nil { + unregistered = append(unregistered, "move.MoveCancelerHandler") + } if o.ShipmentRejectShipmentHandler == nil { unregistered = append(unregistered, "shipment.RejectShipmentHandler") } @@ -1102,6 +1118,10 @@ func (o *MymoveAPI) initHandlerCache() { o.handlers["POST"] = make(map[string]http.Handler) } o.handlers["POST"]["/report-violations/{reportID}"] = report_violations.NewAssociateReportViolations(o.context, o.ReportViolationsAssociateReportViolationsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/payment-requests/{paymentRequestID}/bulkDownload"] = payment_requests.NewBulkDownload(o.context, o.PaymentRequestsBulkDownloadHandler) if o.handlers["PATCH"] == nil { o.handlers["PATCH"] = make(map[string]http.Handler) } @@ -1325,6 +1345,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) } + o.handlers["POST"]["/moves/{moveID}/cancel"] = move.NewMoveCanceler(o.context, o.MoveMoveCancelerHandler) + if o.handlers["POST"] == nil { + o.handlers["POST"] = make(map[string]http.Handler) + } o.handlers["POST"]["/shipments/{shipmentID}/reject"] = shipment.NewRejectShipment(o.context, o.ShipmentRejectShipmentHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go index 2d9e9e81890..20744a2c85b 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_parameters.go @@ -51,6 +51,10 @@ type GetServicesCounselingQueueParams struct { In: query */ CloseoutLocation *string + /*filters using a counselingOffice name of the move + In: query + */ + CounselingOffice *string /*filters the name of the destination duty location on the orders In: query */ @@ -164,6 +168,11 @@ func (o *GetServicesCounselingQueueParams) BindRequest(r *http.Request, route *m res = append(res, err) } + qCounselingOffice, qhkCounselingOffice, _ := qs.GetOK("counselingOffice") + if err := o.bindCounselingOffice(qCounselingOffice, qhkCounselingOffice, route.Formats); err != nil { + res = append(res, err) + } + qDestinationDutyLocation, qhkDestinationDutyLocation, _ := qs.GetOK("destinationDutyLocation") if err := o.bindDestinationDutyLocation(qDestinationDutyLocation, qhkDestinationDutyLocation, route.Formats); err != nil { res = append(res, err) @@ -355,6 +364,24 @@ func (o *GetServicesCounselingQueueParams) bindCloseoutLocation(rawData []string return nil } +// bindCounselingOffice binds and validates parameter CounselingOffice from query. +func (o *GetServicesCounselingQueueParams) bindCounselingOffice(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + o.CounselingOffice = &raw + + return nil +} + // bindDestinationDutyLocation binds and validates parameter DestinationDutyLocation from query. func (o *GetServicesCounselingQueueParams) bindDestinationDutyLocation(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string @@ -724,7 +751,7 @@ func (o *GetServicesCounselingQueueParams) bindSort(rawData []string, hasKey boo // validateSort carries on validations for parameter Sort func (o *GetServicesCounselingQueueParams) validateSort(formats strfmt.Registry) error { - if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"lastName", "dodID", "emplid", "branch", "locator", "status", "requestedMoveDate", "submittedAt", "originGBLOC", "originDutyLocation", "destinationDutyLocation", "ppmType", "closeoutInitiated", "closeoutLocation", "ppmStatus", "assignedTo"}, true); err != nil { + if err := validate.EnumCase("sort", "query", *o.Sort, []interface{}{"lastName", "dodID", "emplid", "branch", "locator", "status", "requestedMoveDate", "submittedAt", "originGBLOC", "originDutyLocation", "destinationDutyLocation", "ppmType", "closeoutInitiated", "closeoutLocation", "ppmStatus", "counselingOffice", "assignedTo"}, true); err != nil { return err } diff --git a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go index 9b480f5c538..5f1d7d96c2c 100644 --- a/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go +++ b/pkg/gen/ghcapi/ghcoperations/queues/get_services_counseling_queue_urlbuilder.go @@ -20,6 +20,7 @@ type GetServicesCounselingQueueURL struct { Branch *string CloseoutInitiated *strfmt.DateTime CloseoutLocation *string + CounselingOffice *string DestinationDutyLocation *string DodID *string Emplid *string @@ -106,6 +107,14 @@ func (o *GetServicesCounselingQueueURL) Build() (*url.URL, error) { qs.Set("closeoutLocation", closeoutLocationQ) } + var counselingOfficeQ string + if o.CounselingOffice != nil { + counselingOfficeQ = *o.CounselingOffice + } + if counselingOfficeQ != "" { + qs.Set("counselingOffice", counselingOfficeQ) + } + var destinationDutyLocationQ string if o.DestinationDutyLocation != nil { destinationDutyLocationQ = *o.DestinationDutyLocation diff --git a/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices.go b/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices.go index 13c260a5655..fd19356220f 100644 --- a/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices.go +++ b/pkg/gen/ghcapi/ghcoperations/transportation_office/get_transportation_offices.go @@ -32,9 +32,9 @@ func NewGetTransportationOffices(ctx *middleware.Context, handler GetTransportat /* GetTransportationOffices swagger:route GET /transportation-offices transportationOffice getTransportationOffices -# Returns the transportation offices matching the search query +# Returns the transportation offices matching the search query that is enabled for PPM closeout -Returns the transportation offices matching the search query +Returns the transportation offices matching the search query that is enabled for PPM closeout */ type GetTransportationOffices struct { Context *middleware.Context diff --git a/pkg/gen/ghcmessages/create_customer_payload.go b/pkg/gen/ghcmessages/create_customer_payload.go index d9d28d6c570..68ece54fb88 100644 --- a/pkg/gen/ghcmessages/create_customer_payload.go +++ b/pkg/gen/ghcmessages/create_customer_payload.go @@ -37,8 +37,9 @@ type CreateCustomerPayload struct { CreateOktaAccount bool `json:"createOktaAccount,omitempty"` // edipi - // Example: John - Edipi *string `json:"edipi,omitempty"` + // Example: 1234567890 + // Max Length: 10 + Edipi string `json:"edipi,omitempty"` // email is preferred EmailIsPreferred bool `json:"emailIsPreferred,omitempty"` @@ -102,6 +103,10 @@ func (m *CreateCustomerPayload) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateEdipi(formats); err != nil { + res = append(res, err) + } + if err := m.validateEmplid(formats); err != nil { res = append(res, err) } @@ -174,6 +179,18 @@ func (m *CreateCustomerPayload) validateBackupMailingAddress(formats strfmt.Regi return nil } +func (m *CreateCustomerPayload) validateEdipi(formats strfmt.Registry) error { + if swag.IsZero(m.Edipi) { // not required + return nil + } + + if err := validate.MaxLength("edipi", "body", m.Edipi, 10); err != nil { + return err + } + + return nil +} + func (m *CreateCustomerPayload) validateEmplid(formats strfmt.Registry) error { if swag.IsZero(m.Emplid) { // not required return nil diff --git a/pkg/gen/ghcmessages/customer.go b/pkg/gen/ghcmessages/customer.go index 2bac3e7d0b3..c4034e3cfe2 100644 --- a/pkg/gen/ghcmessages/customer.go +++ b/pkg/gen/ghcmessages/customer.go @@ -34,12 +34,12 @@ type Customer struct { // current address CurrentAddress *Address `json:"current_address,omitempty"` - // dod ID - DodID string `json:"dodID,omitempty"` - // e tag ETag string `json:"eTag,omitempty"` + // edipi + Edipi string `json:"edipi,omitempty"` + // email // Pattern: ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ Email *string `json:"email,omitempty"` diff --git a/pkg/gen/ghcmessages/p_p_m_shipment_status.go b/pkg/gen/ghcmessages/p_p_m_shipment_status.go index f976a0c7c5c..c0c00accade 100644 --- a/pkg/gen/ghcmessages/p_p_m_shipment_status.go +++ b/pkg/gen/ghcmessages/p_p_m_shipment_status.go @@ -53,6 +53,9 @@ const ( // PPMShipmentStatusCLOSEOUTCOMPLETE captures enum value "CLOSEOUT_COMPLETE" PPMShipmentStatusCLOSEOUTCOMPLETE PPMShipmentStatus = "CLOSEOUT_COMPLETE" + + // PPMShipmentStatusCANCELED captures enum value "CANCELED" + PPMShipmentStatusCANCELED PPMShipmentStatus = "CANCELED" ) // for schema @@ -60,7 +63,7 @@ var pPMShipmentStatusEnum []interface{} func init() { var res []PPMShipmentStatus - if err := json.Unmarshal([]byte(`["DRAFT","SUBMITTED","WAITING_ON_CUSTOMER","NEEDS_ADVANCE_APPROVAL","NEEDS_CLOSEOUT","CLOSEOUT_COMPLETE"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["DRAFT","SUBMITTED","WAITING_ON_CUSTOMER","NEEDS_ADVANCE_APPROVAL","NEEDS_CLOSEOUT","CLOSEOUT_COMPLETE","CANCELED"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/ghcmessages/queue_move.go b/pkg/gen/ghcmessages/queue_move.go index dd6f511866b..f7c831bff29 100644 --- a/pkg/gen/ghcmessages/queue_move.go +++ b/pkg/gen/ghcmessages/queue_move.go @@ -34,6 +34,9 @@ type QueueMove struct { // closeout location CloseoutLocation *string `json:"closeoutLocation,omitempty"` + // counseling office + CounselingOffice *string `json:"counselingOffice,omitempty"` + // customer Customer *Customer `json:"customer,omitempty"` diff --git a/pkg/gen/ghcmessages/service_item_param_name.go b/pkg/gen/ghcmessages/service_item_param_name.go index dd10c8003cd..eff0f3d2734 100644 --- a/pkg/gen/ghcmessages/service_item_param_name.go +++ b/pkg/gen/ghcmessages/service_item_param_name.go @@ -236,6 +236,9 @@ const ( // ServiceItemParamNameUncappedRequestTotal captures enum value "UncappedRequestTotal" ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" + + // ServiceItemParamNameLockedPriceCents captures enum value "LockedPriceCents" + ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" ) // for schema @@ -243,7 +246,7 @@ var serviceItemParamNameEnum []interface{} func init() { var res []ServiceItemParamName - if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal","LockedPriceCents"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index 794f7f59f10..6f380caa91a 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -3987,7 +3987,7 @@ func init() { "type": "string", "format": "telephone", "title": "Alternate phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, @@ -6335,7 +6335,8 @@ func init() { "WAITING_ON_CUSTOMER", "NEEDS_ADVANCE_APPROVAL", "NEEDS_CLOSEOUT", - "CLOSEOUT_COMPLETE" + "CLOSEOUT_COMPLETE", + "CANCELED" ], "readOnly": true }, @@ -6429,7 +6430,7 @@ func init() { "type": "string", "format": "telephone", "title": "Alternate Phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, @@ -6905,7 +6906,7 @@ func init() { "type": "string", "format": "telephone", "title": "Secondary Phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, @@ -12588,7 +12589,7 @@ func init() { "type": "string", "format": "telephone", "title": "Alternate phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, @@ -14940,7 +14941,8 @@ func init() { "WAITING_ON_CUSTOMER", "NEEDS_ADVANCE_APPROVAL", "NEEDS_CLOSEOUT", - "CLOSEOUT_COMPLETE" + "CLOSEOUT_COMPLETE", + "CANCELED" ], "readOnly": true }, @@ -15034,7 +15036,7 @@ func init() { "type": "string", "format": "telephone", "title": "Alternate Phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, @@ -15512,7 +15514,7 @@ func init() { "type": "string", "format": "telephone", "title": "Secondary Phone", - "pattern": "^[2-9]\\d{2}-\\d{3}-\\d{4}$", + "pattern": "^([2-9]\\d{2}-\\d{3}-\\d{4})?$", "x-nullable": true, "example": "212-555-5555" }, diff --git a/pkg/gen/internalmessages/create_service_member_payload.go b/pkg/gen/internalmessages/create_service_member_payload.go index a2b48dce311..737d5a0a897 100644 --- a/pkg/gen/internalmessages/create_service_member_payload.go +++ b/pkg/gen/internalmessages/create_service_member_payload.go @@ -68,7 +68,7 @@ type CreateServiceMemberPayload struct { // Alternate phone // Example: 212-555-5555 - // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + // Pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ SecondaryTelephone *string `json:"secondary_telephone,omitempty"` // Suffix @@ -261,7 +261,7 @@ func (m *CreateServiceMemberPayload) validateSecondaryTelephone(formats strfmt.R return nil } - if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^[2-9]\d{2}-\d{3}-\d{4}$`); err != nil { + if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^([2-9]\d{2}-\d{3}-\d{4})?$`); err != nil { return err } diff --git a/pkg/gen/internalmessages/p_p_m_shipment_status.go b/pkg/gen/internalmessages/p_p_m_shipment_status.go index f509efa3730..eade223927b 100644 --- a/pkg/gen/internalmessages/p_p_m_shipment_status.go +++ b/pkg/gen/internalmessages/p_p_m_shipment_status.go @@ -53,6 +53,9 @@ const ( // PPMShipmentStatusCLOSEOUTCOMPLETE captures enum value "CLOSEOUT_COMPLETE" PPMShipmentStatusCLOSEOUTCOMPLETE PPMShipmentStatus = "CLOSEOUT_COMPLETE" + + // PPMShipmentStatusCANCELED captures enum value "CANCELED" + PPMShipmentStatusCANCELED PPMShipmentStatus = "CANCELED" ) // for schema @@ -60,7 +63,7 @@ var pPMShipmentStatusEnum []interface{} func init() { var res []PPMShipmentStatus - if err := json.Unmarshal([]byte(`["DRAFT","SUBMITTED","WAITING_ON_CUSTOMER","NEEDS_ADVANCE_APPROVAL","NEEDS_CLOSEOUT","CLOSEOUT_COMPLETE"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["DRAFT","SUBMITTED","WAITING_ON_CUSTOMER","NEEDS_ADVANCE_APPROVAL","NEEDS_CLOSEOUT","CLOSEOUT_COMPLETE","CANCELED"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/internalmessages/patch_service_member_payload.go b/pkg/gen/internalmessages/patch_service_member_payload.go index ec9b127dcda..a85374ad1eb 100644 --- a/pkg/gen/internalmessages/patch_service_member_payload.go +++ b/pkg/gen/internalmessages/patch_service_member_payload.go @@ -72,7 +72,7 @@ type PatchServiceMemberPayload struct { // Alternate Phone // Example: 212-555-5555 - // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + // Pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ SecondaryTelephone *string `json:"secondary_telephone,omitempty"` // Suffix @@ -266,7 +266,7 @@ func (m *PatchServiceMemberPayload) validateSecondaryTelephone(formats strfmt.Re return nil } - if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^[2-9]\d{2}-\d{3}-\d{4}$`); err != nil { + if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^([2-9]\d{2}-\d{3}-\d{4})?$`); err != nil { return err } diff --git a/pkg/gen/internalmessages/service_member_payload.go b/pkg/gen/internalmessages/service_member_payload.go index 463243292ff..a288cfba291 100644 --- a/pkg/gen/internalmessages/service_member_payload.go +++ b/pkg/gen/internalmessages/service_member_payload.go @@ -95,7 +95,7 @@ type ServiceMemberPayload struct { // Secondary Phone // Example: 212-555-5555 - // Pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + // Pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ SecondaryTelephone *string `json:"secondary_telephone,omitempty"` // Suffix @@ -411,7 +411,7 @@ func (m *ServiceMemberPayload) validateSecondaryTelephone(formats strfmt.Registr return nil } - if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^[2-9]\d{2}-\d{3}-\d{4}$`); err != nil { + if err := validate.Pattern("secondary_telephone", "body", *m.SecondaryTelephone, `^([2-9]\d{2}-\d{3}-\d{4})?$`); err != nil { return err } diff --git a/pkg/gen/primeapi/embedded_spec.go b/pkg/gen/primeapi/embedded_spec.go index 324f50de3c4..07968eff65b 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -3092,7 +3092,8 @@ func init() { "WAITING_ON_CUSTOMER", "NEEDS_ADVANCE_APPROVAL", "NEEDS_CLOSEOUT", - "CLOSEOUT_COMPLETE" + "CLOSEOUT_COMPLETE", + "CANCELED" ], "readOnly": true }, @@ -3598,7 +3599,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -7795,7 +7797,8 @@ func init() { "WAITING_ON_CUSTOMER", "NEEDS_ADVANCE_APPROVAL", "NEEDS_CLOSEOUT", - "CLOSEOUT_COMPLETE" + "CLOSEOUT_COMPLETE", + "CANCELED" ], "readOnly": true }, @@ -8291,7 +8294,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { diff --git a/pkg/gen/primemessages/p_p_m_shipment_status.go b/pkg/gen/primemessages/p_p_m_shipment_status.go index 371337b4482..8c3914c3f73 100644 --- a/pkg/gen/primemessages/p_p_m_shipment_status.go +++ b/pkg/gen/primemessages/p_p_m_shipment_status.go @@ -53,6 +53,9 @@ const ( // PPMShipmentStatusCLOSEOUTCOMPLETE captures enum value "CLOSEOUT_COMPLETE" PPMShipmentStatusCLOSEOUTCOMPLETE PPMShipmentStatus = "CLOSEOUT_COMPLETE" + + // PPMShipmentStatusCANCELED captures enum value "CANCELED" + PPMShipmentStatusCANCELED PPMShipmentStatus = "CANCELED" ) // for schema @@ -60,7 +63,7 @@ var pPMShipmentStatusEnum []interface{} func init() { var res []PPMShipmentStatus - if err := json.Unmarshal([]byte(`["DRAFT","SUBMITTED","WAITING_ON_CUSTOMER","NEEDS_ADVANCE_APPROVAL","NEEDS_CLOSEOUT","CLOSEOUT_COMPLETE"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["DRAFT","SUBMITTED","WAITING_ON_CUSTOMER","NEEDS_ADVANCE_APPROVAL","NEEDS_CLOSEOUT","CLOSEOUT_COMPLETE","CANCELED"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primemessages/service_item_param_name.go b/pkg/gen/primemessages/service_item_param_name.go index 7f8e128b151..d43a63a69f7 100644 --- a/pkg/gen/primemessages/service_item_param_name.go +++ b/pkg/gen/primemessages/service_item_param_name.go @@ -236,6 +236,9 @@ const ( // ServiceItemParamNameUncappedRequestTotal captures enum value "UncappedRequestTotal" ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" + + // ServiceItemParamNameLockedPriceCents captures enum value "LockedPriceCents" + ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" ) // for schema @@ -243,7 +246,7 @@ var serviceItemParamNameEnum []interface{} func init() { var res []ServiceItemParamName - if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal","LockedPriceCents"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev2api/embedded_spec.go b/pkg/gen/primev2api/embedded_spec.go index 6dff4a08fa2..c06735be87b 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -2111,7 +2111,8 @@ func init() { "WAITING_ON_CUSTOMER", "NEEDS_ADVANCE_APPROVAL", "NEEDS_CLOSEOUT", - "CLOSEOUT_COMPLETE" + "CLOSEOUT_COMPLETE", + "CANCELED" ], "readOnly": true }, @@ -2586,7 +2587,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -5449,7 +5451,8 @@ func init() { "WAITING_ON_CUSTOMER", "NEEDS_ADVANCE_APPROVAL", "NEEDS_CLOSEOUT", - "CLOSEOUT_COMPLETE" + "CLOSEOUT_COMPLETE", + "CANCELED" ], "readOnly": true }, @@ -5924,7 +5927,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { diff --git a/pkg/gen/primev2messages/p_p_m_shipment_status.go b/pkg/gen/primev2messages/p_p_m_shipment_status.go index 439086abcaa..ccbf00e05d1 100644 --- a/pkg/gen/primev2messages/p_p_m_shipment_status.go +++ b/pkg/gen/primev2messages/p_p_m_shipment_status.go @@ -53,6 +53,9 @@ const ( // PPMShipmentStatusCLOSEOUTCOMPLETE captures enum value "CLOSEOUT_COMPLETE" PPMShipmentStatusCLOSEOUTCOMPLETE PPMShipmentStatus = "CLOSEOUT_COMPLETE" + + // PPMShipmentStatusCANCELED captures enum value "CANCELED" + PPMShipmentStatusCANCELED PPMShipmentStatus = "CANCELED" ) // for schema @@ -60,7 +63,7 @@ var pPMShipmentStatusEnum []interface{} func init() { var res []PPMShipmentStatus - if err := json.Unmarshal([]byte(`["DRAFT","SUBMITTED","WAITING_ON_CUSTOMER","NEEDS_ADVANCE_APPROVAL","NEEDS_CLOSEOUT","CLOSEOUT_COMPLETE"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["DRAFT","SUBMITTED","WAITING_ON_CUSTOMER","NEEDS_ADVANCE_APPROVAL","NEEDS_CLOSEOUT","CLOSEOUT_COMPLETE","CANCELED"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev2messages/service_item_param_name.go b/pkg/gen/primev2messages/service_item_param_name.go index a6939ad00f4..fb2d4097030 100644 --- a/pkg/gen/primev2messages/service_item_param_name.go +++ b/pkg/gen/primev2messages/service_item_param_name.go @@ -236,6 +236,9 @@ const ( // ServiceItemParamNameUncappedRequestTotal captures enum value "UncappedRequestTotal" ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" + + // ServiceItemParamNameLockedPriceCents captures enum value "LockedPriceCents" + ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" ) // for schema @@ -243,7 +246,7 @@ var serviceItemParamNameEnum []interface{} func init() { var res []ServiceItemParamName - if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal","LockedPriceCents"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev3api/embedded_spec.go b/pkg/gen/primev3api/embedded_spec.go index ae197314629..ebf4567cbcd 100644 --- a/pkg/gen/primev3api/embedded_spec.go +++ b/pkg/gen/primev3api/embedded_spec.go @@ -1653,20 +1653,10 @@ func init() { "x-omitempty": false }, "secondaryDeliveryAddress": { - "description": "A second delivery address for this shipment, if the customer entered one. An optional field.", - "allOf": [ - { - "$ref": "#/definitions/Address" - } - ] + "$ref": "#/definitions/Address" }, "secondaryPickupAddress": { - "description": "A second pickup address for this shipment, if the customer entered one. An optional field.", - "allOf": [ - { - "$ref": "#/definitions/Address" - } - ] + "$ref": "#/definitions/Address" }, "shipmentType": { "$ref": "#/definitions/MTOShipmentType" @@ -1697,6 +1687,12 @@ func init() { } ] }, + "tertiaryDeliveryAddress": { + "$ref": "#/definitions/Address" + }, + "tertiaryPickupAddress": { + "$ref": "#/definitions/Address" + }, "updatedAt": { "type": "string", "format": "date-time", @@ -2044,6 +2040,16 @@ func init() { "x-nullable": true, "x-omitempty": false }, + "hasTertiaryDestinationAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, + "hasTertiaryPickupAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, "id": { "description": "The primary unique identifier of this PPM shipment", "type": "string", @@ -2141,6 +2147,12 @@ func init() { "x-nullable": true, "x-omitempty": false }, + "tertiaryDestinationAddress": { + "$ref": "#/definitions/Address" + }, + "tertiaryPickupAddress": { + "$ref": "#/definitions/Address" + }, "updatedAt": { "description": "The timestamp of when a property of this object was last updated (UTC)", "type": "string", @@ -2159,7 +2171,8 @@ func init() { "WAITING_ON_CUSTOMER", "NEEDS_ADVANCE_APPROVAL", "NEEDS_CLOSEOUT", - "CLOSEOUT_COMPLETE" + "CLOSEOUT_COMPLETE", + "CANCELED" ], "readOnly": true }, @@ -2634,7 +2647,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -3088,6 +3102,22 @@ func init() { "$ref": "#/definitions/StorageFacility" } ] + }, + "tertiaryDeliveryAddress": { + "description": "A third delivery address for this shipment, if the customer entered one. An optional field.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "tertiaryPickupAddress": { + "description": "A third pickup address for this shipment, if the customer entered one. An optional field.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] } } }, @@ -3142,6 +3172,16 @@ func init() { "x-nullable": true, "x-omitempty": false }, + "hasTertiaryDestinationAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, + "hasTertiaryPickupAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, "pickupAddress": { "description": "The address of the origin location where goods are being moved from.\n", "allOf": [ @@ -3208,6 +3248,22 @@ func init() { "description": "The estimated weight of the pro-gear being moved belonging to a spouse.", "type": "integer", "x-nullable": true + }, + "tertiaryDestinationAddress": { + "description": "An optional third address near the destination where goods will be dropped off.\n", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "tertiaryPickupAddress": { + "description": "An optional third pickup location near the origin where additional goods exist.\n", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] } } }, @@ -5083,20 +5139,10 @@ func init() { "x-omitempty": false }, "secondaryDeliveryAddress": { - "description": "A second delivery address for this shipment, if the customer entered one. An optional field.", - "allOf": [ - { - "$ref": "#/definitions/Address" - } - ] + "$ref": "#/definitions/Address" }, "secondaryPickupAddress": { - "description": "A second pickup address for this shipment, if the customer entered one. An optional field.", - "allOf": [ - { - "$ref": "#/definitions/Address" - } - ] + "$ref": "#/definitions/Address" }, "shipmentType": { "$ref": "#/definitions/MTOShipmentType" @@ -5127,6 +5173,12 @@ func init() { } ] }, + "tertiaryDeliveryAddress": { + "$ref": "#/definitions/Address" + }, + "tertiaryPickupAddress": { + "$ref": "#/definitions/Address" + }, "updatedAt": { "type": "string", "format": "date-time", @@ -5474,6 +5526,16 @@ func init() { "x-nullable": true, "x-omitempty": false }, + "hasTertiaryDestinationAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, + "hasTertiaryPickupAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, "id": { "description": "The primary unique identifier of this PPM shipment", "type": "string", @@ -5571,6 +5633,12 @@ func init() { "x-nullable": true, "x-omitempty": false }, + "tertiaryDestinationAddress": { + "$ref": "#/definitions/Address" + }, + "tertiaryPickupAddress": { + "$ref": "#/definitions/Address" + }, "updatedAt": { "description": "The timestamp of when a property of this object was last updated (UTC)", "type": "string", @@ -5589,7 +5657,8 @@ func init() { "WAITING_ON_CUSTOMER", "NEEDS_ADVANCE_APPROVAL", "NEEDS_CLOSEOUT", - "CLOSEOUT_COMPLETE" + "CLOSEOUT_COMPLETE", + "CANCELED" ], "readOnly": true }, @@ -6064,7 +6133,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -6520,6 +6590,22 @@ func init() { "$ref": "#/definitions/StorageFacility" } ] + }, + "tertiaryDeliveryAddress": { + "description": "A third delivery address for this shipment, if the customer entered one. An optional field.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "tertiaryPickupAddress": { + "description": "A third pickup address for this shipment, if the customer entered one. An optional field.", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] } } }, @@ -6574,6 +6660,16 @@ func init() { "x-nullable": true, "x-omitempty": false }, + "hasTertiaryDestinationAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, + "hasTertiaryPickupAddress": { + "type": "boolean", + "x-nullable": true, + "x-omitempty": false + }, "pickupAddress": { "description": "The address of the origin location where goods are being moved from.\n", "allOf": [ @@ -6640,6 +6736,22 @@ func init() { "description": "The estimated weight of the pro-gear being moved belonging to a spouse.", "type": "integer", "x-nullable": true + }, + "tertiaryDestinationAddress": { + "description": "An optional third address near the destination where goods will be dropped off.\n", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] + }, + "tertiaryPickupAddress": { + "description": "An optional third pickup location near the origin where additional goods exist.\n", + "allOf": [ + { + "$ref": "#/definitions/Address" + } + ] } } }, diff --git a/pkg/gen/primev3messages/m_t_o_shipment_without_service_items.go b/pkg/gen/primev3messages/m_t_o_shipment_without_service_items.go index 9c5c695d851..c6f76b2c8b2 100644 --- a/pkg/gen/primev3messages/m_t_o_shipment_without_service_items.go +++ b/pkg/gen/primev3messages/m_t_o_shipment_without_service_items.go @@ -188,15 +188,11 @@ type MTOShipmentWithoutServiceItems struct { // Format: date ScheduledPickupDate *strfmt.Date `json:"scheduledPickupDate"` - // A second delivery address for this shipment, if the customer entered one. An optional field. - SecondaryDeliveryAddress struct { - Address - } `json:"secondaryDeliveryAddress,omitempty"` + // secondary delivery address + SecondaryDeliveryAddress *Address `json:"secondaryDeliveryAddress,omitempty"` - // A second pickup address for this shipment, if the customer entered one. An optional field. - SecondaryPickupAddress struct { - Address - } `json:"secondaryPickupAddress,omitempty"` + // secondary pickup address + SecondaryPickupAddress *Address `json:"secondaryPickupAddress,omitempty"` // shipment type ShipmentType MTOShipmentType `json:"shipmentType,omitempty"` @@ -213,6 +209,12 @@ type MTOShipmentWithoutServiceItems struct { // storage facility StorageFacility *StorageFacility `json:"storageFacility,omitempty"` + // tertiary delivery address + TertiaryDeliveryAddress *Address `json:"tertiaryDeliveryAddress,omitempty"` + + // tertiary pickup address + TertiaryPickupAddress *Address `json:"tertiaryPickupAddress,omitempty"` + // updated at // Read Only: true // Format: date-time @@ -343,6 +345,14 @@ func (m *MTOShipmentWithoutServiceItems) Validate(formats strfmt.Registry) error res = append(res, err) } + if err := m.validateTertiaryDeliveryAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTertiaryPickupAddress(formats); err != nil { + res = append(res, err) + } + if err := m.validateUpdatedAt(formats); err != nil { res = append(res, err) } @@ -671,6 +681,17 @@ func (m *MTOShipmentWithoutServiceItems) validateSecondaryDeliveryAddress(format return nil } + if m.SecondaryDeliveryAddress != nil { + if err := m.SecondaryDeliveryAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("secondaryDeliveryAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("secondaryDeliveryAddress") + } + return err + } + } + return nil } @@ -679,6 +700,17 @@ func (m *MTOShipmentWithoutServiceItems) validateSecondaryPickupAddress(formats return nil } + if m.SecondaryPickupAddress != nil { + if err := m.SecondaryPickupAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("secondaryPickupAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("secondaryPickupAddress") + } + return err + } + } + return nil } @@ -789,6 +821,44 @@ func (m *MTOShipmentWithoutServiceItems) validateStorageFacility(formats strfmt. return nil } +func (m *MTOShipmentWithoutServiceItems) validateTertiaryDeliveryAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryDeliveryAddress) { // not required + return nil + } + + if m.TertiaryDeliveryAddress != nil { + if err := m.TertiaryDeliveryAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tertiaryDeliveryAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tertiaryDeliveryAddress") + } + return err + } + } + + return nil +} + +func (m *MTOShipmentWithoutServiceItems) validateTertiaryPickupAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryPickupAddress) { // not required + return nil + } + + if m.TertiaryPickupAddress != nil { + if err := m.TertiaryPickupAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tertiaryPickupAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tertiaryPickupAddress") + } + return err + } + } + + return nil +} + func (m *MTOShipmentWithoutServiceItems) validateUpdatedAt(formats strfmt.Registry) error { if swag.IsZero(m.UpdatedAt) { // not required return nil @@ -905,6 +975,14 @@ func (m *MTOShipmentWithoutServiceItems) ContextValidate(ctx context.Context, fo res = append(res, err) } + if err := m.contextValidateTertiaryDeliveryAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateTertiaryPickupAddress(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateUpdatedAt(ctx, formats); err != nil { res = append(res, err) } @@ -1133,11 +1211,43 @@ func (m *MTOShipmentWithoutServiceItems) contextValidateReweigh(ctx context.Cont func (m *MTOShipmentWithoutServiceItems) contextValidateSecondaryDeliveryAddress(ctx context.Context, formats strfmt.Registry) error { + if m.SecondaryDeliveryAddress != nil { + + if swag.IsZero(m.SecondaryDeliveryAddress) { // not required + return nil + } + + if err := m.SecondaryDeliveryAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("secondaryDeliveryAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("secondaryDeliveryAddress") + } + return err + } + } + return nil } func (m *MTOShipmentWithoutServiceItems) contextValidateSecondaryPickupAddress(ctx context.Context, formats strfmt.Registry) error { + if m.SecondaryPickupAddress != nil { + + if swag.IsZero(m.SecondaryPickupAddress) { // not required + return nil + } + + if err := m.SecondaryPickupAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("secondaryPickupAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("secondaryPickupAddress") + } + return err + } + } + return nil } @@ -1203,6 +1313,48 @@ func (m *MTOShipmentWithoutServiceItems) contextValidateStorageFacility(ctx cont return nil } +func (m *MTOShipmentWithoutServiceItems) contextValidateTertiaryDeliveryAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.TertiaryDeliveryAddress != nil { + + if swag.IsZero(m.TertiaryDeliveryAddress) { // not required + return nil + } + + if err := m.TertiaryDeliveryAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tertiaryDeliveryAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tertiaryDeliveryAddress") + } + return err + } + } + + return nil +} + +func (m *MTOShipmentWithoutServiceItems) contextValidateTertiaryPickupAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.TertiaryPickupAddress != nil { + + if swag.IsZero(m.TertiaryPickupAddress) { // not required + return nil + } + + if err := m.TertiaryPickupAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tertiaryPickupAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tertiaryPickupAddress") + } + return err + } + } + + return nil +} + func (m *MTOShipmentWithoutServiceItems) contextValidateUpdatedAt(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "updatedAt", "body", strfmt.DateTime(m.UpdatedAt)); err != nil { diff --git a/pkg/gen/primev3messages/p_p_m_shipment.go b/pkg/gen/primev3messages/p_p_m_shipment.go index bb1acd53881..99a834a1303 100644 --- a/pkg/gen/primev3messages/p_p_m_shipment.go +++ b/pkg/gen/primev3messages/p_p_m_shipment.go @@ -97,6 +97,12 @@ type PPMShipment struct { // has secondary pickup address HasSecondaryPickupAddress *bool `json:"hasSecondaryPickupAddress"` + // has tertiary destination address + HasTertiaryDestinationAddress *bool `json:"hasTertiaryDestinationAddress"` + + // has tertiary pickup address + HasTertiaryPickupAddress *bool `json:"hasTertiaryPickupAddress"` + // The primary unique identifier of this PPM shipment // Example: 1f2270c7-7166-40ae-981e-b200ebdf3054 // Required: true @@ -164,6 +170,12 @@ type PPMShipment struct { // Format: date-time SubmittedAt *strfmt.DateTime `json:"submittedAt"` + // tertiary destination address + TertiaryDestinationAddress *Address `json:"tertiaryDestinationAddress,omitempty"` + + // tertiary pickup address + TertiaryPickupAddress *Address `json:"tertiaryPickupAddress,omitempty"` + // The timestamp of when a property of this object was last updated (UTC) // Read Only: true // Format: date-time @@ -254,6 +266,14 @@ func (m *PPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateTertiaryDestinationAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTertiaryPickupAddress(formats); err != nil { + res = append(res, err) + } + if err := m.validateUpdatedAt(formats); err != nil { res = append(res, err) } @@ -545,6 +565,44 @@ func (m *PPMShipment) validateSubmittedAt(formats strfmt.Registry) error { return nil } +func (m *PPMShipment) validateTertiaryDestinationAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryDestinationAddress) { // not required + return nil + } + + if m.TertiaryDestinationAddress != nil { + if err := m.TertiaryDestinationAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tertiaryDestinationAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tertiaryDestinationAddress") + } + return err + } + } + + return nil +} + +func (m *PPMShipment) validateTertiaryPickupAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryPickupAddress) { // not required + return nil + } + + if m.TertiaryPickupAddress != nil { + if err := m.TertiaryPickupAddress.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tertiaryPickupAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tertiaryPickupAddress") + } + return err + } + } + + return nil +} + func (m *PPMShipment) validateUpdatedAt(formats strfmt.Registry) error { if swag.IsZero(m.UpdatedAt) { // not required return nil @@ -601,6 +659,14 @@ func (m *PPMShipment) ContextValidate(ctx context.Context, formats strfmt.Regist res = append(res, err) } + if err := m.contextValidateTertiaryDestinationAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateTertiaryPickupAddress(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateUpdatedAt(ctx, formats); err != nil { res = append(res, err) } @@ -758,6 +824,48 @@ func (m *PPMShipment) contextValidateStatus(ctx context.Context, formats strfmt. return nil } +func (m *PPMShipment) contextValidateTertiaryDestinationAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.TertiaryDestinationAddress != nil { + + if swag.IsZero(m.TertiaryDestinationAddress) { // not required + return nil + } + + if err := m.TertiaryDestinationAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tertiaryDestinationAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tertiaryDestinationAddress") + } + return err + } + } + + return nil +} + +func (m *PPMShipment) contextValidateTertiaryPickupAddress(ctx context.Context, formats strfmt.Registry) error { + + if m.TertiaryPickupAddress != nil { + + if swag.IsZero(m.TertiaryPickupAddress) { // not required + return nil + } + + if err := m.TertiaryPickupAddress.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("tertiaryPickupAddress") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("tertiaryPickupAddress") + } + return err + } + } + + return nil +} + func (m *PPMShipment) contextValidateUpdatedAt(ctx context.Context, formats strfmt.Registry) error { if err := validate.ReadOnly(ctx, "updatedAt", "body", strfmt.DateTime(m.UpdatedAt)); err != nil { diff --git a/pkg/gen/primev3messages/p_p_m_shipment_status.go b/pkg/gen/primev3messages/p_p_m_shipment_status.go index e594db1bc55..a2f5bd086cd 100644 --- a/pkg/gen/primev3messages/p_p_m_shipment_status.go +++ b/pkg/gen/primev3messages/p_p_m_shipment_status.go @@ -53,6 +53,9 @@ const ( // PPMShipmentStatusCLOSEOUTCOMPLETE captures enum value "CLOSEOUT_COMPLETE" PPMShipmentStatusCLOSEOUTCOMPLETE PPMShipmentStatus = "CLOSEOUT_COMPLETE" + + // PPMShipmentStatusCANCELED captures enum value "CANCELED" + PPMShipmentStatusCANCELED PPMShipmentStatus = "CANCELED" ) // for schema @@ -60,7 +63,7 @@ var pPMShipmentStatusEnum []interface{} func init() { var res []PPMShipmentStatus - if err := json.Unmarshal([]byte(`["DRAFT","SUBMITTED","WAITING_ON_CUSTOMER","NEEDS_ADVANCE_APPROVAL","NEEDS_CLOSEOUT","CLOSEOUT_COMPLETE"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["DRAFT","SUBMITTED","WAITING_ON_CUSTOMER","NEEDS_ADVANCE_APPROVAL","NEEDS_CLOSEOUT","CLOSEOUT_COMPLETE","CANCELED"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev3messages/service_item_param_name.go b/pkg/gen/primev3messages/service_item_param_name.go index ab53b71f111..a7e2fdf7ea3 100644 --- a/pkg/gen/primev3messages/service_item_param_name.go +++ b/pkg/gen/primev3messages/service_item_param_name.go @@ -236,6 +236,9 @@ const ( // ServiceItemParamNameUncappedRequestTotal captures enum value "UncappedRequestTotal" ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" + + // ServiceItemParamNameLockedPriceCents captures enum value "LockedPriceCents" + ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" ) // for schema @@ -243,7 +246,7 @@ var serviceItemParamNameEnum []interface{} func init() { var res []ServiceItemParamName - if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal"]`), &res); err != nil { + if err := json.Unmarshal([]byte(`["ActualPickupDate","ContractCode","ContractYearName","CubicFeetBilled","CubicFeetCrating","DimensionHeight","DimensionLength","DimensionWidth","DistanceZip","DistanceZipSITDest","DistanceZipSITOrigin","EIAFuelPrice","EscalationCompounded","FSCMultiplier","FSCPriceDifferenceInCents","FSCWeightBasedDistanceMultiplier","IsPeak","MarketDest","MarketOrigin","MTOAvailableToPrimeAt","NTSPackingFactor","NumberDaysSIT","PriceAreaDest","PriceAreaIntlDest","PriceAreaIntlOrigin","PriceAreaOrigin","PriceRateOrFactor","PSI_LinehaulDom","PSI_LinehaulDomPrice","PSI_LinehaulShort","PSI_LinehaulShortPrice","PSI_PriceDomDest","PSI_PriceDomDestPrice","PSI_PriceDomOrigin","PSI_PriceDomOriginPrice","PSI_ShippingLinehaulIntlCO","PSI_ShippingLinehaulIntlCOPrice","PSI_ShippingLinehaulIntlOC","PSI_ShippingLinehaulIntlOCPrice","PSI_ShippingLinehaulIntlOO","PSI_ShippingLinehaulIntlOOPrice","RateAreaNonStdDest","RateAreaNonStdOrigin","ReferenceDate","RequestedPickupDate","ServiceAreaDest","ServiceAreaOrigin","ServicesScheduleDest","ServicesScheduleOrigin","SITPaymentRequestEnd","SITPaymentRequestStart","SITScheduleDest","SITScheduleOrigin","SITServiceAreaDest","SITServiceAreaOrigin","WeightAdjusted","WeightBilled","WeightEstimated","WeightOriginal","WeightReweigh","ZipDestAddress","ZipPickupAddress","ZipSITDestHHGFinalAddress","ZipSITDestHHGOriginalAddress","ZipSITOriginHHGActualAddress","ZipSITOriginHHGOriginalAddress","StandaloneCrate","StandaloneCrateCap","UncappedRequestTotal","LockedPriceCents"]`), &res); err != nil { panic(err) } for _, v := range res { diff --git a/pkg/gen/primev3messages/update_m_t_o_shipment.go b/pkg/gen/primev3messages/update_m_t_o_shipment.go index 037dbf4557f..df0c18d8763 100644 --- a/pkg/gen/primev3messages/update_m_t_o_shipment.go +++ b/pkg/gen/primev3messages/update_m_t_o_shipment.go @@ -112,6 +112,16 @@ type UpdateMTOShipment struct { // storage facility StorageFacility *StorageFacility `json:"storageFacility,omitempty"` + + // A third delivery address for this shipment, if the customer entered one. An optional field. + TertiaryDeliveryAddress struct { + Address + } `json:"tertiaryDeliveryAddress,omitempty"` + + // A third pickup address for this shipment, if the customer entered one. An optional field. + TertiaryPickupAddress struct { + Address + } `json:"tertiaryPickupAddress,omitempty"` } // Validate validates this update m t o shipment @@ -178,6 +188,14 @@ func (m *UpdateMTOShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateTertiaryDeliveryAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTertiaryPickupAddress(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -374,6 +392,22 @@ func (m *UpdateMTOShipment) validateStorageFacility(formats strfmt.Registry) err return nil } +func (m *UpdateMTOShipment) validateTertiaryDeliveryAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryDeliveryAddress) { // not required + return nil + } + + return nil +} + +func (m *UpdateMTOShipment) validateTertiaryPickupAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryPickupAddress) { // not required + return nil + } + + return nil +} + // ContextValidate validate this update m t o shipment based on the context it is used func (m *UpdateMTOShipment) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -410,6 +444,14 @@ func (m *UpdateMTOShipment) ContextValidate(ctx context.Context, formats strfmt. res = append(res, err) } + if err := m.contextValidateTertiaryDeliveryAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateTertiaryPickupAddress(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -517,6 +559,16 @@ func (m *UpdateMTOShipment) contextValidateStorageFacility(ctx context.Context, return nil } +func (m *UpdateMTOShipment) contextValidateTertiaryDeliveryAddress(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +func (m *UpdateMTOShipment) contextValidateTertiaryPickupAddress(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + // MarshalBinary interface implementation func (m *UpdateMTOShipment) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/gen/primev3messages/update_p_p_m_shipment.go b/pkg/gen/primev3messages/update_p_p_m_shipment.go index 6778ab0cb05..ead98493601 100644 --- a/pkg/gen/primev3messages/update_p_p_m_shipment.go +++ b/pkg/gen/primev3messages/update_p_p_m_shipment.go @@ -44,6 +44,12 @@ type UpdatePPMShipment struct { // has secondary pickup address HasSecondaryPickupAddress *bool `json:"hasSecondaryPickupAddress"` + // has tertiary destination address + HasTertiaryDestinationAddress *bool `json:"hasTertiaryDestinationAddress"` + + // has tertiary pickup address + HasTertiaryPickupAddress *bool `json:"hasTertiaryPickupAddress"` + // The address of the origin location where goods are being moved from. // PickupAddress struct { @@ -88,6 +94,18 @@ type UpdatePPMShipment struct { // The estimated weight of the pro-gear being moved belonging to a spouse. SpouseProGearWeight *int64 `json:"spouseProGearWeight,omitempty"` + + // An optional third address near the destination where goods will be dropped off. + // + TertiaryDestinationAddress struct { + Address + } `json:"tertiaryDestinationAddress,omitempty"` + + // An optional third pickup location near the origin where additional goods exist. + // + TertiaryPickupAddress struct { + Address + } `json:"tertiaryPickupAddress,omitempty"` } // Validate validates this update p p m shipment @@ -126,6 +144,14 @@ func (m *UpdatePPMShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validateTertiaryDestinationAddress(formats); err != nil { + res = append(res, err) + } + + if err := m.validateTertiaryPickupAddress(formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -219,6 +245,22 @@ func (m *UpdatePPMShipment) validateSitLocation(formats strfmt.Registry) error { return nil } +func (m *UpdatePPMShipment) validateTertiaryDestinationAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryDestinationAddress) { // not required + return nil + } + + return nil +} + +func (m *UpdatePPMShipment) validateTertiaryPickupAddress(formats strfmt.Registry) error { + if swag.IsZero(m.TertiaryPickupAddress) { // not required + return nil + } + + return nil +} + // ContextValidate validate this update p p m shipment based on the context it is used func (m *UpdatePPMShipment) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error @@ -243,6 +285,14 @@ func (m *UpdatePPMShipment) ContextValidate(ctx context.Context, formats strfmt. res = append(res, err) } + if err := m.contextValidateTertiaryDestinationAddress(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidateTertiaryPickupAddress(ctx, formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { return errors.CompositeValidationError(res...) } @@ -290,6 +340,16 @@ func (m *UpdatePPMShipment) contextValidateSitLocation(ctx context.Context, form return nil } +func (m *UpdatePPMShipment) contextValidateTertiaryDestinationAddress(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + +func (m *UpdatePPMShipment) contextValidateTertiaryPickupAddress(ctx context.Context, formats strfmt.Registry) error { + + return nil +} + // MarshalBinary interface implementation func (m *UpdatePPMShipment) MarshalBinary() ([]byte, error) { if m == nil { diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 24772afccd1..96167bbecfe 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -667,6 +667,17 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { order.NewOrderUpdater(moveRouter), } + ghcAPI.MoveMoveCancelerHandler = MoveCancelerHandler{ + handlerConfig, + move.NewMoveCanceler(), + } + + paymentRequestBulkDownloadCreator := paymentrequest.NewPaymentRequestBulkDownloadCreator(pdfGenerator) + ghcAPI.PaymentRequestsBulkDownloadHandler = PaymentRequestBulkDownloadHandler{ + handlerConfig, + paymentRequestBulkDownloadCreator, + } + dateSelectionChecker := dateservice.NewDateSelectionChecker() ghcAPI.CalendarIsDateWeekendHolidayHandler = IsDateWeekendHolidayHandler{handlerConfig, dateSelectionChecker} diff --git a/pkg/handlers/ghcapi/customer.go b/pkg/handlers/ghcapi/customer.go index 51e57284945..7452fa9589c 100644 --- a/pkg/handlers/ghcapi/customer.go +++ b/pkg/handlers/ghcapi/customer.go @@ -163,7 +163,6 @@ func (h CreateCustomerWithOktaOptionHandler) Handle(params customercodeop.Create payload := params.Body var err error var serviceMembers []models.ServiceMember - var edipi *string var dodidUniqueFeatureFlag bool // evaluating feature flag to see if we need to check if the DODID exists already @@ -177,30 +176,31 @@ func (h CreateCustomerWithOktaOptionHandler) Handle(params customercodeop.Create } if dodidUniqueFeatureFlag { - if payload.Edipi == nil || *payload.Edipi == "" { - edipi = nil - } else { - query := `SELECT service_members.edipi + query := `SELECT service_members.edipi FROM service_members WHERE service_members.edipi = $1` - err := appCtx.DB().RawQuery(query, payload.Edipi).All(&serviceMembers) - if err != nil { - errorMsg := apperror.NewBadDataError("error when checking for existing service member") - payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) - return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg - } else if len(serviceMembers) > 0 { - errorMsg := apperror.NewConflictError(h.GetTraceIDFromRequest(params.HTTPRequest), "Service member with this DODID already exists. Please use a different DODID number.") - payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) - return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg - } + err := appCtx.DB().RawQuery(query, payload.Edipi).All(&serviceMembers) + if err != nil { + errorMsg := apperror.NewBadDataError("error when checking for existing service member") + payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) + return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg + } else if len(serviceMembers) > 0 { + errorMsg := apperror.NewConflictError(h.GetTraceIDFromRequest(params.HTTPRequest), "Service member with this DODID already exists. Please use a different DODID number.") + payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) + return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg } + } - if len(serviceMembers) == 0 { - edipi = params.Body.Edipi + // Endpoint specific EDIPI and EMPLID check + // The following validation currently is only intended for the customer creation + // conducted by an office user such as the Service Counselor + if payload.Affiliation != nil && *payload.Affiliation == ghcmessages.AffiliationCOASTGUARD { + // EMPLID cannot be null + if payload.Emplid == nil { + errorMsg := apperror.NewConflictError(h.GetTraceIDFromRequest(params.HTTPRequest), "Service members from the Coast Guard require an EMPLID for creation.") + payload := payloadForValidationError("Unable to create a customer", errorMsg.Error(), h.GetTraceIDFromRequest(params.HTTPRequest), validate.NewErrors()) + return customercodeop.NewCreateCustomerWithOktaOptionUnprocessableEntity().WithPayload(payload), errorMsg } - } else { - // If the feature flag is not enabled, we will just set the dodid and continue - edipi = params.Body.Edipi } var newServiceMember models.ServiceMember @@ -250,18 +250,11 @@ func (h CreateCustomerWithOktaOptionHandler) Handle(params customercodeop.Create residentialAddress := addressModelFromPayload(&payload.ResidentialAddress.Address) backupMailingAddress := addressModelFromPayload(&payload.BackupMailingAddress.Address) - var emplid *string - if *payload.Emplid == "" { - emplid = nil - } else { - emplid = payload.Emplid - } - // Create a new serviceMember using the userID newServiceMember = models.ServiceMember{ UserID: userID, - Edipi: edipi, - Emplid: emplid, + Edipi: &payload.Edipi, + Emplid: payload.Emplid, Affiliation: (*models.ServiceMemberAffiliation)(payload.Affiliation), FirstName: &payload.FirstName, MiddleName: payload.MiddleName, diff --git a/pkg/handlers/ghcapi/customer_test.go b/pkg/handlers/ghcapi/customer_test.go index ca46f85b2a5..3a6a3305172 100644 --- a/pkg/handlers/ghcapi/customer_test.go +++ b/pkg/handlers/ghcapi/customer_test.go @@ -53,7 +53,7 @@ func (suite *HandlerSuite) TestGetCustomerHandlerIntegration() { suite.NoError(getCustomerPayload.Validate(strfmt.Default)) suite.Equal(strfmt.UUID(customer.ID.String()), getCustomerPayload.ID) - suite.Equal(*customer.Edipi, getCustomerPayload.DodID) + suite.Equal(*customer.Edipi, getCustomerPayload.Edipi) suite.Equal(strfmt.UUID(customer.UserID.String()), getCustomerPayload.UserID) suite.Equal(customer.Affiliation.String(), getCustomerPayload.Agency) suite.Equal(customer.PersonalEmail, getCustomerPayload.Email) @@ -162,7 +162,7 @@ func (suite *HandlerSuite) TestCreateCustomerWithOktaOptionHandler() { FirstName: "First", Telephone: handlers.FmtString("223-455-3399"), Affiliation: &affiliation, - Edipi: handlers.FmtString(""), + Edipi: "", Emplid: handlers.FmtString(""), PersonalEmail: *handlers.FmtString("email@email.com"), BackupContact: &ghcmessages.BackupContact{ @@ -260,7 +260,7 @@ func (suite *HandlerSuite) TestCreateCustomerWithOktaOptionHandler() { FirstName: "First", Telephone: handlers.FmtString("223-455-3399"), Affiliation: &affiliation, - Edipi: customer.Edipi, + Edipi: *customer.Edipi, PersonalEmail: *handlers.FmtString("email@email.com"), BackupContact: &ghcmessages.BackupContact{ Name: handlers.FmtString("New Backup Contact"), @@ -298,6 +298,81 @@ func (suite *HandlerSuite) TestCreateCustomerWithOktaOptionHandler() { response := handler.Handle(params) suite.Assertions.IsType(&customerops.CreateCustomerWithOktaOptionUnprocessableEntity{}, response) }) + + suite.Run("Unable to create customer of affiliation Coast Guard with no EMPLID", func() { + // in order to call the endpoint, we need to be an authenticated office user that's a SC + officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) + officeUser.User.Roles = append(officeUser.User.Roles, roles.Role{ + RoleType: roles.RoleTypeServicesCounselor, + }) + + // Build provider + provider, err := factory.BuildOktaProvider(officeProviderName) + suite.NoError(err) + + mockAndActivateOktaEndpoints(provider) + + residentialAddress := ghcmessages.Address{ + StreetAddress1: handlers.FmtString("123 New Street"), + City: handlers.FmtString("Newcity"), + State: handlers.FmtString("MA"), + PostalCode: handlers.FmtString("02110"), + } + + backupAddress := ghcmessages.Address{ + StreetAddress1: handlers.FmtString("123 Backup Street"), + City: handlers.FmtString("Backupcity"), + State: handlers.FmtString("MA"), + PostalCode: handlers.FmtString("02115"), + } + + affiliation := ghcmessages.AffiliationCOASTGUARD + + body := &ghcmessages.CreateCustomerPayload{ + LastName: "Last", + FirstName: "First", + Telephone: handlers.FmtString("223-455-3399"), + Affiliation: &affiliation, + Edipi: "1234567890", + PersonalEmail: *handlers.FmtString("email@email.com"), + BackupContact: &ghcmessages.BackupContact{ + Name: handlers.FmtString("New Backup Contact"), + Phone: handlers.FmtString("445-345-1212"), + Email: handlers.FmtString("newbackup@mail.com"), + }, + ResidentialAddress: struct { + ghcmessages.Address + }{ + Address: residentialAddress, + }, + BackupMailingAddress: struct { + ghcmessages.Address + }{ + Address: backupAddress, + }, + CreateOktaAccount: true, + // when CacUser is false, this indicates a non-CAC user so CacValidated is set to true + CacUser: false, + } + + defer goth.ClearProviders() + goth.UseProviders(provider) + + request := httptest.NewRequest("POST", "/customer", nil) + request = suite.AuthenticateOfficeRequest(request, officeUser) + params := customerops.CreateCustomerWithOktaOptionParams{ + HTTPRequest: request, + Body: body, + } + handlerConfig := suite.HandlerConfig() + handler := CreateCustomerWithOktaOptionHandler{ + handlerConfig, + } + response := handler.Handle(params) + suite.Assertions.IsType(&customerops.CreateCustomerWithOktaOptionUnprocessableEntity{}, response) + failedToCreateCustomerPayload := response.(*customerops.CreateCustomerWithOktaOptionUnprocessableEntity).Payload.ClientError.Detail + suite.Equal("ID: 00000000-0000-0000-0000-000000000000 is in a conflicting state Service members from the Coast Guard require an EMPLID for creation.", *failedToCreateCustomerPayload) + }) } func (suite *HandlerSuite) TestSearchCustomersHandler() { diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 4885837d787..933d9431941 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -505,7 +505,7 @@ func Customer(customer *models.ServiceMember) *ghcmessages.Customer { payload := ghcmessages.Customer{ Agency: swag.StringValue((*string)(customer.Affiliation)), CurrentAddress: Address(customer.ResidentialAddress), - DodID: swag.StringValue(customer.Edipi), + Edipi: swag.StringValue(customer.Edipi), Email: customer.PersonalEmail, FirstName: swag.StringValue(customer.FirstName), ID: strfmt.UUID(customer.ID.String()), @@ -2039,6 +2039,10 @@ func QueueMoves(moves []models.Move) *ghcmessages.QueueMoves { for i, move := range moves { customer := move.Orders.ServiceMember + var transportationOffice string + if move.CounselingOffice != nil { + transportationOffice = move.CounselingOffice.Name + } var validMTOShipments []models.MTOShipment var earliestRequestedPickup *time.Time // we can't easily modify our sql query to find the earliest shipment pickup date so we must do it here @@ -2110,7 +2114,7 @@ func QueueMoves(moves []models.Move) *ghcmessages.QueueMoves { LockedByOfficeUser: OfficeUser(move.LockedByOfficeUser), LockExpiresAt: handlers.FmtDateTimePtr(move.LockExpiresAt), PpmStatus: ghcmessages.PPMStatus(ppmStatus), - AssignedTo: AssignedOfficeUser(move.SCAssignedUser), + CounselingOffice: &transportationOffice, } } return &queueMoves diff --git a/pkg/handlers/ghcapi/move.go b/pkg/handlers/ghcapi/move.go index bbb837065bb..a5cb708844c 100644 --- a/pkg/handlers/ghcapi/move.go +++ b/pkg/handlers/ghcapi/move.go @@ -281,6 +281,74 @@ func (h UploadAdditionalDocumentsHandler) Handle(params moveop.UploadAdditionalD }) } +type MoveCancelerHandler struct { + handlers.HandlerConfig + services.MoveCanceler +} + +func (h MoveCancelerHandler) Handle(params moveop.MoveCancelerParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + moveID := uuid.FromStringOrNil(params.MoveID.String()) + + move, err := h.MoveCanceler.CancelMove(appCtx, moveID) + if err != nil { + appCtx.Logger().Error("MoveCancelerHandler error", zap.Error(err)) + switch err.(type) { + case apperror.NotFoundError: + return moveop.NewMoveCancelerNotFound(), err + case apperror.PreconditionFailedError: + return moveop.NewMoveCancelerPreconditionFailed(), err + case apperror.InvalidInputError: + return moveop.NewMoveCancelerUnprocessableEntity(), err + case apperror.ConflictError: + return moveop.NewMoveCancelerConflict(), err + default: + return moveop.NewMoveCancelerInternalServerError(), err + } + } + + payload, err := payloads.Move(move, h.FileStorer()) + if err != nil { + return nil, err + } + return moveop.NewMoveCancelerOK().WithPayload(payload), nil + }) +} + +func payloadForUploadModelFromAdditionalDocumentsUpload(storer storage.FileStorer, upload models.Upload, url string) (*ghcmessages.Upload, error) { + uploadPayload := &ghcmessages.Upload{ + ID: handlers.FmtUUIDValue(upload.ID), + Filename: upload.Filename, + ContentType: upload.ContentType, + URL: strfmt.URI(url), + Bytes: upload.Bytes, + CreatedAt: strfmt.DateTime(upload.CreatedAt), + UpdatedAt: strfmt.DateTime(upload.UpdatedAt), + } + tags, err := storer.Tags(upload.StorageKey) + if err != nil || len(tags) == 0 { + uploadPayload.Status = "PROCESSING" + } else { + uploadPayload.Status = tags["av-status"] + } + return uploadPayload, nil +} + +func getRole(role string) roles.RoleType { + var roleType roles.RoleType + switch role { + case "services_counselor": + roleType = roles.RoleTypeServicesCounselor + case "task_ordering_officer": + roleType = roles.RoleTypeTOO + case "task_invoicing_officer": + roleType = roles.RoleTypeTIO + } + + return roleType +} + type DeleteAssignedOfficeUserHandler struct { handlers.HandlerConfig services.MoveAssignedOfficeUserUpdater @@ -343,36 +411,3 @@ func (h UpdateAssignedOfficeUserHandler) Handle(params moveop.UpdateAssignedOffi return moveop.NewUpdateAssignedOfficeUserOK().WithPayload(payload), nil }) } - -func payloadForUploadModelFromAdditionalDocumentsUpload(storer storage.FileStorer, upload models.Upload, url string) (*ghcmessages.Upload, error) { - uploadPayload := &ghcmessages.Upload{ - ID: handlers.FmtUUIDValue(upload.ID), - Filename: upload.Filename, - ContentType: upload.ContentType, - URL: strfmt.URI(url), - Bytes: upload.Bytes, - CreatedAt: strfmt.DateTime(upload.CreatedAt), - UpdatedAt: strfmt.DateTime(upload.UpdatedAt), - } - tags, err := storer.Tags(upload.StorageKey) - if err != nil || len(tags) == 0 { - uploadPayload.Status = "PROCESSING" - } else { - uploadPayload.Status = tags["av-status"] - } - return uploadPayload, nil -} - -func getRole(role string) roles.RoleType { - var roleType roles.RoleType - switch role { - case "services_counselor": - roleType = roles.RoleTypeServicesCounselor - case "task_ordering_officer": - roleType = roles.RoleTypeTOO - case "task_invoicing_officer": - roleType = roles.RoleTypeTIO - } - - return roleType -} diff --git a/pkg/handlers/ghcapi/mto_service_items.go b/pkg/handlers/ghcapi/mto_service_items.go index 160f96db12b..33b34ae1373 100644 --- a/pkg/handlers/ghcapi/mto_service_items.go +++ b/pkg/handlers/ghcapi/mto_service_items.go @@ -17,7 +17,6 @@ import ( "github.com/transcom/mymove/pkg/handlers" "github.com/transcom/mymove/pkg/handlers/ghcapi/internal/payloads" "github.com/transcom/mymove/pkg/models" - serviceparamlookups "github.com/transcom/mymove/pkg/payment_request/service_param_value_lookups" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/services/audit" "github.com/transcom/mymove/pkg/services/event" @@ -384,7 +383,6 @@ func (h ListMTOServiceItemsHandler) Handle(params mtoserviceitemop.ListMTOServic } if len(indices) > 0 { - contract, err := serviceparamlookups.FetchContract(appCtx, *moveTaskOrder.AvailableToPrimeAt) if err != nil { return mtoserviceitemop.NewListMTOServiceItemsInternalServerError(), err } @@ -394,9 +392,9 @@ func (h ListMTOServiceItemsHandler) Handle(params mtoserviceitemop.ListMTOServic var displayParams services.PricingDisplayParams var err error if serviceItems[index].ReService.Code == "CS" { - price, displayParams, err = h.counselingPricer.Price(appCtx, contract.Code, *moveTaskOrder.AvailableToPrimeAt) + price, displayParams, err = h.counselingPricer.Price(appCtx, serviceItems[index].LockedPriceCents) } else if serviceItems[index].ReService.Code == "MS" { - price, displayParams, err = h.moveManagementPricer.Price(appCtx, contract.Code, *moveTaskOrder.AvailableToPrimeAt) + price, displayParams, err = h.moveManagementPricer.Price(appCtx, serviceItems[index].LockedPriceCents) } for _, param := range displayParams { diff --git a/pkg/handlers/ghcapi/orders.go b/pkg/handlers/ghcapi/orders.go index fb6d627c2dd..a4231d9df06 100644 --- a/pkg/handlers/ghcapi/orders.go +++ b/pkg/handlers/ghcapi/orders.go @@ -313,26 +313,24 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. Show: models.BoolPointer(true), Status: &status, } + if !appCtx.Session().OfficeUserID.IsNil() { + officeUser, err := models.FetchOfficeUserByID(appCtx.DB(), appCtx.Session().OfficeUserID) + if err != nil { + err = apperror.NewBadDataError("Unable to fetch office user.") + appCtx.Logger().Error(err.Error()) + return orderop.NewCreateOrderUnprocessableEntity(), err + } else { + moveOptions.CounselingOfficeID = &officeUser.TransportationOfficeID + } + } if newOrder.OrdersType == "SAFETY" { - // if creating a Safety move, clear out the DoDID and OktaID for the customer + // if creating a Safety move, clear out the OktaID for the customer since they won't log into MilMove err = models.UpdateUserOktaID(appCtx.DB(), &newOrder.ServiceMember.User, "") if err != nil { appCtx.Logger().Error("Authorization error updating user", zap.Error(err)) return orderop.NewUpdateOrderInternalServerError(), err } - - err = models.UpdateServiceMemberDoDID(appCtx.DB(), &newOrder.ServiceMember, nil) - if err != nil { - appCtx.Logger().Error("Authorization error updating service member", zap.Error(err)) - return orderop.NewUpdateOrderInternalServerError(), err - } - - err = models.UpdateServiceMemberEMPLID(appCtx.DB(), &newOrder.ServiceMember, nil) - if err != nil { - appCtx.Logger().Error("Authorization error updating service member", zap.Error(err)) - return orderop.NewUpdateOrderInternalServerError(), err - } } newMove, verrs, err := newOrder.CreateNewMove(appCtx.DB(), moveOptions) diff --git a/pkg/handlers/ghcapi/orders_test.go b/pkg/handlers/ghcapi/orders_test.go index 351c09087d5..35e92e4611e 100644 --- a/pkg/handlers/ghcapi/orders_test.go +++ b/pkg/handlers/ghcapi/orders_test.go @@ -37,21 +37,22 @@ import ( ) func (suite *HandlerSuite) TestCreateOrder() { - sm := factory.BuildExtendedServiceMember(suite.DB(), nil, nil) + sm := factory.BuildExtendedServiceMember(suite.AppContextForTest().DB(), nil, nil) + officeUser := factory.BuildOfficeUserWithRoles(suite.AppContextForTest().DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - originDutyLocation := factory.BuildDutyLocation(suite.DB(), []factory.Customization{ + originDutyLocation := factory.BuildDutyLocation(suite.AppContextForTest().DB(), []factory.Customization{ { Model: models.DutyLocation{ Name: "Not Yuma AFB", }, }, }, nil) - dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.DB()) - factory.FetchOrBuildPostalCodeToGBLOC(suite.DB(), dutyLocation.Address.PostalCode, "KKFA") - factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) + dutyLocation := factory.FetchOrBuildCurrentDutyLocation(suite.AppContextForTest().DB()) + factory.FetchOrBuildPostalCodeToGBLOC(suite.AppContextForTest().DB(), dutyLocation.Address.PostalCode, "KKFA") + factory.FetchOrBuildDefaultContractor(suite.AppContextForTest().DB(), nil, nil) req := httptest.NewRequest("POST", "/orders", nil) - req = suite.AuthenticateRequest(req, sm) + req = suite.AuthenticateOfficeRequest(req, officeUser) hasDependents := true spouseHasProGear := true diff --git a/pkg/handlers/ghcapi/payment_request.go b/pkg/handlers/ghcapi/payment_request.go index 757993d2669..622d38b1abc 100644 --- a/pkg/handlers/ghcapi/payment_request.go +++ b/pkg/handlers/ghcapi/payment_request.go @@ -2,6 +2,7 @@ package ghcapi import ( "fmt" + "io" "reflect" "time" @@ -261,3 +262,39 @@ func (h ShipmentsSITBalanceHandler) Handle( return paymentrequestop.NewGetShipmentsPaymentSITBalanceOK().WithPayload(payload), nil }) } + +type PaymentRequestBulkDownloadHandler struct { + handlers.HandlerConfig + services.PaymentRequestBulkDownloadCreator +} + +func (h PaymentRequestBulkDownloadHandler) Handle(params paymentrequestop.BulkDownloadParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + logger := appCtx.Logger() + + paymentRequestID, err := uuid.FromString(params.PaymentRequestID) + if err != nil { + errInstance := fmt.Sprintf("Instance: %s", h.GetTraceIDFromRequest(params.HTTPRequest)) + + errPayload := &ghcmessages.Error{Message: &errInstance} + + appCtx.Logger().Error(err.Error()) + return paymentrequestop.NewBulkDownloadBadRequest().WithPayload(errPayload), err + } + + paymentRequestPacket, err := h.PaymentRequestBulkDownloadCreator.CreatePaymentRequestBulkDownload(appCtx, paymentRequestID) + if err != nil { + logger.Error("Error creating Payment Request Downloads Packet", zap.Error(err)) + errInstance := fmt.Sprintf("Instance: %s", h.GetTraceIDFromRequest(params.HTTPRequest)) + errPayload := &ghcmessages.Error{Message: &errInstance} + return paymentrequestop.NewBulkDownloadInternalServerError(). + WithPayload(errPayload), err + } + + payload := io.NopCloser(paymentRequestPacket) + filename := fmt.Sprintf("inline; filename=\"PaymentRequestBulkPacket-%s.pdf\"", time.Now().Format("01-02-2006_15-04-05")) + + return paymentrequestop.NewBulkDownloadOK().WithContentDisposition(filename).WithPayload(payload), nil + }) +} diff --git a/pkg/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go index f3ac08f2813..30c96df7960 100644 --- a/pkg/handlers/ghcapi/queues.go +++ b/pkg/handlers/ghcapi/queues.go @@ -367,6 +367,7 @@ func (h GetServicesCounselingQueueHandler) Handle( CloseoutLocation: params.CloseoutLocation, OrderType: params.OrderType, PPMStatus: params.PpmStatus, + CounselingOffice: params.CounselingOffice, SCAssignedUser: params.AssignedTo, } diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go index f1efe5028fe..4cc6aaaa1da 100644 --- a/pkg/handlers/ghcapi/queues_test.go +++ b/pkg/handlers/ghcapi/queues_test.go @@ -833,7 +833,7 @@ func (suite *HandlerSuite) TestGetMoveQueuesHandlerCustomerInfoFilters() { result := payload.QueueMoves[0] suite.Len(payload.QueueMoves, 1) - suite.Equal("11111", result.Customer.DodID) + suite.Equal("11111", result.Customer.Edipi) }) suite.Run("returns results matching Move ID search term", func() { @@ -1525,7 +1525,7 @@ func (suite *HandlerSuite) TestGetServicesCounselingQueueHandler() { suite.Len(payload.QueueMoves, 2) suite.Equal(order.ServiceMember.ID.String(), result1.Customer.ID.String()) - suite.Equal(*order.ServiceMember.Edipi, result1.Customer.DodID) + suite.Equal(*order.ServiceMember.Edipi, result1.Customer.Edipi) suite.Equal(subtestData.needsCounselingMove.Locator, result1.Locator) suite.EqualValues(subtestData.needsCounselingMove.Status, result1.Status) suite.Equal(subtestData.needsCounselingEarliestShipment.RequestedPickupDate.Format(time.RFC3339Nano), (time.Time)(*result1.RequestedMoveDate).Format(time.RFC3339Nano)) diff --git a/pkg/handlers/ghcapi/tranportation_offices.go b/pkg/handlers/ghcapi/tranportation_offices.go index d60f4b0a70a..405580923bb 100644 --- a/pkg/handlers/ghcapi/tranportation_offices.go +++ b/pkg/handlers/ghcapi/tranportation_offices.go @@ -20,7 +20,10 @@ func (h GetTransportationOfficesHandler) Handle(params transportationofficeop.Ge return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { - transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, false) + // B-21022: forPpm param is set true. This is used by PPM closeout widget. Need to ensure certain offices are included/excluded + // if location has ppm closedout enabled. + transportationOffices, err := h.TransportationOfficesFetcher.GetTransportationOffices(appCtx, params.Search, true) + if err != nil { appCtx.Logger().Error("Error searching for Transportation Offices: ", zap.Error(err)) return transportationofficeop.NewGetTransportationOfficesInternalServerError(), err diff --git a/pkg/handlers/internalapi/mto_shipment_test.go b/pkg/handlers/internalapi/mto_shipment_test.go index 34de5b5c327..5d69a4c3343 100644 --- a/pkg/handlers/internalapi/mto_shipment_test.go +++ b/pkg/handlers/internalapi/mto_shipment_test.go @@ -413,6 +413,8 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerV1() { ntsrShipmentType := internalmessages.MTOShipmentTypeHHGOUTOFNTSDOMESTIC params.Body.ShipmentType = &ntsrShipmentType params.Body.RequestedPickupDate = strfmt.Date(time.Time{}) + params.Body.PickupAddress = nil + params.Body.SecondaryPickupAddress = nil response := subtestData.handler.Handle(subtestData.params) @@ -425,8 +427,6 @@ func (suite *HandlerSuite) TestCreateMTOShipmentHandlerV1() { suite.Equal(ntsrShipmentType, createdShipment.ShipmentType) suite.Equal(models.MTOShipmentStatusSubmitted, models.MTOShipmentStatus(createdShipment.Status)) suite.Equal(*params.Body.CustomerRemarks, *createdShipment.CustomerRemarks) - suite.Equal(*params.Body.PickupAddress.StreetAddress1, *createdShipment.PickupAddress.StreetAddress1) - suite.Equal(*params.Body.SecondaryPickupAddress.StreetAddress1, *createdShipment.SecondaryPickupAddress.StreetAddress1) suite.Equal(*params.Body.DestinationAddress.StreetAddress1, *createdShipment.DestinationAddress.StreetAddress1) suite.Equal(*params.Body.SecondaryDeliveryAddress.StreetAddress1, *createdShipment.SecondaryDeliveryAddress.StreetAddress1) suite.Nil(createdShipment.RequestedPickupDate) diff --git a/pkg/handlers/internalapi/office_test.go b/pkg/handlers/internalapi/office_test.go index a7995ca1098..810252af0b2 100644 --- a/pkg/handlers/internalapi/office_test.go +++ b/pkg/handlers/internalapi/office_test.go @@ -143,9 +143,11 @@ func (suite *HandlerSuite) TestCancelMoveHandler() { orders := factory.BuildOrder(suite.DB(), nil, nil) factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) moveRouter := moverouter.NewMoveRouter() + office := factory.BuildTransportationOffice(suite.DB(), nil, nil) moveOptions := models.MoveOptions{ - Show: models.BoolPointer(true), + Show: models.BoolPointer(true), + CounselingOfficeID: &office.ID, } move, verrs, err := orders.CreateNewMove(suite.DB(), moveOptions) suite.NoError(err) diff --git a/pkg/handlers/primeapi/mto_shipment_address.go b/pkg/handlers/primeapi/mto_shipment_address.go index 2f050cb947a..b45999955a9 100644 --- a/pkg/handlers/primeapi/mto_shipment_address.go +++ b/pkg/handlers/primeapi/mto_shipment_address.go @@ -38,12 +38,24 @@ func (h UpdateMTOShipmentAddressHandler) Handle(params mtoshipmentops.UpdateMTOS payloads.ClientError(handlers.NotFoundMessage, err.Error(), h.GetTraceIDFromRequest(params.HTTPRequest))), err } + if dbShipment.ShipmentType == models.MTOShipmentTypeHHGIntoNTSDom && + (dbShipment.DestinationAddressID != nil && *dbShipment.DestinationAddressID == addressID) { + return mtoshipmentops.NewUpdateMTOShipmentAddressUnprocessableEntity().WithPayload(payloads.ValidationError( + "Cannot update the destination address of an NTS shipment directly, please update the storage facility address instead", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), err + } + if dbShipment.Status == models.MTOShipmentStatusApproved && - (*dbShipment.DestinationAddressID == addressID) { + (dbShipment.DestinationAddressID != nil && *dbShipment.DestinationAddressID == addressID) { return mtoshipmentops.NewUpdateMTOShipmentAddressUnprocessableEntity().WithPayload(payloads.ValidationError( "This shipment is approved, please use the updateShipmentDestinationAddress endpoint to update the destination address of an approved shipment", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), err } + if dbShipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTSDom && + (*dbShipment.PickupAddressID != uuid.Nil && *dbShipment.PickupAddressID == addressID) { + return mtoshipmentops.NewUpdateMTOShipmentAddressUnprocessableEntity().WithPayload(payloads.ValidationError( + "Cannot update the pickup address of an NTS-Release shipment directly, please update the storage facility address instead", h.GetTraceIDFromRequest(params.HTTPRequest), nil)), err + } + // Get the new address model newAddress := payloads.AddressModel(payload) newAddress.ID = addressID diff --git a/pkg/handlers/primeapi/mto_shipment_address_test.go b/pkg/handlers/primeapi/mto_shipment_address_test.go index 5e6c41aaec8..8a45daa4156 100644 --- a/pkg/handlers/primeapi/mto_shipment_address_test.go +++ b/pkg/handlers/primeapi/mto_shipment_address_test.go @@ -304,4 +304,52 @@ func (suite *HandlerSuite) TestUpdateMTOShipmentAddressHandler() { response := handler.Handle(params) suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) }) + + suite.Run("Fail - Unprocessable due to updating pickup address on NTS-Release shipment", func() { + // Testcase: destination address is updated on a shipment, but shipment is approved + // Expected: UnprocessableEntity error is returned + // Under Test: UpdateMTOShipmentAddress handler + handler, availableMove := setupTestData() + pickupAddress := factory.BuildAddress(suite.DB(), nil, []factory.Trait{factory.GetTraitAddress2}) + address := models.Address{ + ID: pickupAddress.ID, + StreetAddress1: "7 Q St", + City: "Framington", + State: "MA", + PostalCode: "35004", + } + shipment := factory.BuildNTSRShipment(suite.DB(), []factory.Customization{ + { + Model: availableMove, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: pickupAddress, + LinkOnly: true, + Type: &factory.Addresses.PickupAddress, + }, + }, nil) + // Try to update destination address for approved shipment + payload := payloads.Address(&address) + req := httptest.NewRequest("PUT", fmt.Sprintf("/mto-shipments/%s/addresses/%s", shipment.ID.String(), shipment.ID.String()), nil) + params := mtoshipmentops.UpdateMTOShipmentAddressParams{ + HTTPRequest: req, + AddressID: *handlers.FmtUUID(pickupAddress.ID), + MtoShipmentID: *handlers.FmtUUID(shipment.ID), + Body: payload, + IfMatch: etag.GenerateEtag(shipment.DestinationAddress.UpdatedAt), + } + + // Validate incoming payload + suite.NoError(params.Body.Validate(strfmt.Default)) + + // Run handler and check response + response := handler.Handle(params) + suite.IsType(&mtoshipmentops.UpdateMTOShipmentAddressUnprocessableEntity{}, response) + }) } diff --git a/pkg/handlers/primeapiv3/move_task_order_test.go b/pkg/handlers/primeapiv3/move_task_order_test.go index abcd48a9741..e0587fd4bbd 100644 --- a/pkg/handlers/primeapiv3/move_task_order_test.go +++ b/pkg/handlers/primeapiv3/move_task_order_test.go @@ -301,6 +301,8 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { destinationType := models.DestinationTypeHomeOfRecord secondaryDeliveryAddress := factory.BuildAddress(suite.DB(), nil, nil) secondaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) + tertiaryDeliveryAddress := factory.BuildAddress(suite.DB(), nil, nil) + tertiaryPickupAddress := factory.BuildAddress(suite.DB(), nil, nil) now := time.Now() nowDate := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, time.UTC) yesterDate := nowDate.AddDate(0, 0, -1) @@ -331,6 +333,16 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { Type: &factory.Addresses.SecondaryPickupAddress, LinkOnly: true, }, + { + Model: tertiaryDeliveryAddress, + Type: &factory.Addresses.TertiaryDeliveryAddress, + LinkOnly: true, + }, + { + Model: tertiaryPickupAddress, + Type: &factory.Addresses.TertiaryPickupAddress, + LinkOnly: true, + }, { Model: successMove, LinkOnly: true, @@ -389,9 +401,11 @@ func (suite *HandlerSuite) TestGetMoveTaskOrder() { suite.Equal(successShipment.ScheduledDeliveryDate.Format(time.RFC3339), handlers.FmtDatePtrToPop(shipment.ScheduledDeliveryDate).Format(time.RFC3339)) suite.Equal(successShipment.ScheduledPickupDate.Format(time.RFC3339), handlers.FmtDatePtrToPop(shipment.ScheduledPickupDate).Format(time.RFC3339)) - verifyAddressFields(successShipment.SecondaryDeliveryAddress, &shipment.SecondaryDeliveryAddress.Address) - verifyAddressFields(successShipment.SecondaryPickupAddress, &shipment.SecondaryPickupAddress.Address) + verifyAddressFields(successShipment.SecondaryDeliveryAddress, shipment.SecondaryDeliveryAddress) + verifyAddressFields(successShipment.SecondaryPickupAddress, shipment.SecondaryPickupAddress) + verifyAddressFields(successShipment.TertiaryDeliveryAddress, shipment.TertiaryDeliveryAddress) + verifyAddressFields(successShipment.TertiaryPickupAddress, shipment.TertiaryPickupAddress) suite.Equal(string(successShipment.ShipmentType), string(shipment.ShipmentType)) suite.Equal(string(successShipment.Status), shipment.Status) diff --git a/pkg/handlers/primeapiv3/payloads/model_to_payload.go b/pkg/handlers/primeapiv3/payloads/model_to_payload.go index 8d2bd176a7d..fc8017841b9 100644 --- a/pkg/handlers/primeapiv3/payloads/model_to_payload.go +++ b/pkg/handlers/primeapiv3/payloads/model_to_payload.go @@ -491,6 +491,10 @@ func MTOShipmentWithoutServiceItems(mtoShipment *models.MTOShipment) *primev3mes ETag: etag.GenerateEtag(mtoShipment.UpdatedAt), OriginSitAuthEndDate: (*strfmt.Date)(mtoShipment.OriginSITAuthEndDate), DestinationSitAuthEndDate: (*strfmt.Date)(mtoShipment.DestinationSITAuthEndDate), + SecondaryDeliveryAddress: Address(mtoShipment.SecondaryDeliveryAddress), + SecondaryPickupAddress: Address(mtoShipment.SecondaryPickupAddress), + TertiaryDeliveryAddress: Address(mtoShipment.TertiaryDeliveryAddress), + TertiaryPickupAddress: Address(mtoShipment.TertiaryPickupAddress), } // Set up address payloads @@ -504,12 +508,6 @@ func MTOShipmentWithoutServiceItems(mtoShipment *models.MTOShipment) *primev3mes destinationType := primev3messages.DestinationType(*mtoShipment.DestinationType) payload.DestinationType = &destinationType } - if mtoShipment.SecondaryPickupAddress != nil { - payload.SecondaryPickupAddress.Address = *Address(mtoShipment.SecondaryPickupAddress) - } - if mtoShipment.SecondaryDeliveryAddress != nil { - payload.SecondaryDeliveryAddress.Address = *Address(mtoShipment.SecondaryDeliveryAddress) - } if mtoShipment.StorageFacility != nil { payload.StorageFacility = StorageFacility(mtoShipment.StorageFacility) @@ -534,14 +532,22 @@ func MTOShipmentWithoutServiceItems(mtoShipment *models.MTOShipment) *primev3mes if mtoShipment.PPMShipment.SecondaryPickupAddress != nil { payload.PpmShipment.SecondaryPickupAddress = Address(mtoShipment.PPMShipment.SecondaryPickupAddress) } + if mtoShipment.PPMShipment.TertiaryPickupAddress != nil { + payload.PpmShipment.TertiaryPickupAddress = Address(mtoShipment.PPMShipment.TertiaryPickupAddress) + } if mtoShipment.PPMShipment.DestinationAddress != nil { payload.PpmShipment.DestinationAddress = Address(mtoShipment.PPMShipment.DestinationAddress) } if mtoShipment.PPMShipment.SecondaryDestinationAddress != nil { payload.PpmShipment.SecondaryDestinationAddress = Address(mtoShipment.PPMShipment.SecondaryDestinationAddress) } + if mtoShipment.PPMShipment.TertiaryDestinationAddress != nil { + payload.PpmShipment.TertiaryDestinationAddress = Address(mtoShipment.PPMShipment.TertiaryDestinationAddress) + } payload.PpmShipment.HasSecondaryPickupAddress = mtoShipment.PPMShipment.HasSecondaryPickupAddress payload.PpmShipment.HasSecondaryDestinationAddress = mtoShipment.PPMShipment.HasSecondaryDestinationAddress + payload.PpmShipment.HasTertiaryPickupAddress = mtoShipment.PPMShipment.HasTertiaryPickupAddress + payload.PpmShipment.HasTertiaryDestinationAddress = mtoShipment.PPMShipment.HasTertiaryDestinationAddress } return payload diff --git a/pkg/handlers/primeapiv3/payloads/payload_to_model.go b/pkg/handlers/primeapiv3/payloads/payload_to_model.go index 28be5539b61..50588acfb28 100644 --- a/pkg/handlers/primeapiv3/payloads/payload_to_model.go +++ b/pkg/handlers/primeapiv3/payloads/payload_to_model.go @@ -341,6 +341,14 @@ func MTOShipmentModelFromUpdate(mtoShipment *primev3messages.UpdateMTOShipment, model.HasSecondaryPickupAddress = handlers.FmtBool(true) } + addressModel = AddressModel(&mtoShipment.TertiaryPickupAddress.Address) + if addressModel != nil { + model.TertiaryPickupAddress = addressModel + tertiaryPickupAddressID := uuid.FromStringOrNil(addressModel.ID.String()) + model.TertiaryPickupAddressID = &tertiaryPickupAddressID + model.HasTertiaryPickupAddress = handlers.FmtBool(true) + } + addressModel = AddressModel(&mtoShipment.SecondaryDeliveryAddress.Address) if addressModel != nil { model.SecondaryDeliveryAddress = addressModel @@ -349,6 +357,14 @@ func MTOShipmentModelFromUpdate(mtoShipment *primev3messages.UpdateMTOShipment, model.HasSecondaryDeliveryAddress = handlers.FmtBool(true) } + addressModel = AddressModel(&mtoShipment.TertiaryDeliveryAddress.Address) + if addressModel != nil { + model.TertiaryDeliveryAddress = addressModel + tertiaryDeliveryAddressID := uuid.FromStringOrNil(addressModel.ID.String()) + model.TertiaryDeliveryAddressID = &tertiaryDeliveryAddressID + model.HasTertiaryDeliveryAddress = handlers.FmtBool(true) + } + if mtoShipment.PpmShipment != nil { model.PPMShipment = PPMShipmentModelFromUpdate(mtoShipment.PpmShipment) model.PPMShipment.Shipment = *model @@ -391,6 +407,15 @@ func PPMShipmentModelFromUpdate(ppmShipment *primev3messages.UpdatePPMShipment) } } + if ppmShipment.HasTertiaryPickupAddress != nil && *ppmShipment.HasTertiaryPickupAddress { + addressModel = AddressModel(&ppmShipment.TertiaryPickupAddress.Address) + if addressModel != nil { + model.TertiaryPickupAddress = addressModel + tertiaryPickupAddressID := uuid.FromStringOrNil(addressModel.ID.String()) + model.TertiaryPickupAddressID = &tertiaryPickupAddressID + } + } + addressModel = AddressModel(&ppmShipment.DestinationAddress.Address) if addressModel != nil { model.DestinationAddress = addressModel @@ -406,6 +431,15 @@ func PPMShipmentModelFromUpdate(ppmShipment *primev3messages.UpdatePPMShipment) } } + if ppmShipment.HasTertiaryDestinationAddress != nil && *ppmShipment.HasTertiaryDestinationAddress { + addressModel = AddressModel(&ppmShipment.TertiaryDestinationAddress.Address) + if addressModel != nil { + model.TertiaryDestinationAddress = addressModel + tertiaryDestinationAddressID := uuid.FromStringOrNil(addressModel.ID.String()) + model.TertiaryDestinationAddressID = &tertiaryDestinationAddressID + } + } + expectedDepartureDate := handlers.FmtDatePtrToPopPtr(ppmShipment.ExpectedDepartureDate) if expectedDepartureDate != nil && !expectedDepartureDate.IsZero() { model.ExpectedDepartureDate = *expectedDepartureDate diff --git a/pkg/models/move.go b/pkg/models/move.go index aef105de3e0..be20cb326a6 100644 --- a/pkg/models/move.go +++ b/pkg/models/move.go @@ -110,8 +110,9 @@ func (m Move) TableName() string { // MoveOptions is used when creating new moves based on parameters type MoveOptions struct { - Show *bool - Status *MoveStatus + Show *bool + Status *MoveStatus + CounselingOfficeID *uuid.UUID } type Moves []Move @@ -296,6 +297,9 @@ func createNewMove(db *pop.Connection, ContractorID: &contractor.ID, ReferenceID: &referenceID, } + if moveOptions.CounselingOfficeID != nil { + move.CounselingOfficeID = moveOptions.CounselingOfficeID + } // only want safety moves move locators to start with SM, so try again if strings.HasPrefix(move.Locator, "SM") { continue @@ -325,7 +329,9 @@ func createNewMove(db *pop.Connection, ContractorID: &contractor.ID, ReferenceID: &referenceID, } - + if moveOptions.CounselingOfficeID != nil { + move.CounselingOfficeID = moveOptions.CounselingOfficeID + } verrs, err := db.ValidateAndCreate(&move) if verrs.HasAny() { return nil, verrs, nil diff --git a/pkg/models/move_test.go b/pkg/models/move_test.go index 9a2216015cd..d0c41baac56 100644 --- a/pkg/models/move_test.go +++ b/pkg/models/move_test.go @@ -37,9 +37,11 @@ func (suite *ModelSuite) TestBasicMoveInstantiation() { func (suite *ModelSuite) TestCreateNewMoveValidLocatorString() { orders := factory.BuildOrder(suite.DB(), nil, nil) factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) + office := factory.BuildTransportationOffice(suite.DB(), nil, nil) moveOptions := m.MoveOptions{ - Show: m.BoolPointer(true), + Show: m.BoolPointer(true), + CounselingOfficeID: &office.ID, } move, verrs, err := orders.CreateNewMove(suite.DB(), moveOptions) suite.NoError(err) @@ -87,10 +89,12 @@ func (suite *ModelSuite) TestFetchMove() { // Set up: Create an HHG move, then fetch it, then move to status completed, fetch again // Expected outcome: Move found, in both cases session, order := setupTestData() + office := factory.BuildTransportationOffice(suite.DB(), nil, nil) // Create HHG Move moveOptions := m.MoveOptions{ - Show: m.BoolPointer(true), + Show: m.BoolPointer(true), + CounselingOfficeID: &office.ID, } move, verrs, err := order.CreateNewMove(suite.DB(), moveOptions) suite.NoError(err) @@ -135,11 +139,13 @@ func (suite *ModelSuite) TestFetchMove() { // Fetch the second user's move, but with the first user logged in. // Expected outcome: Move not found, ErrFetchForbidden session, _ := setupTestData() + office := factory.BuildTransportationOffice(suite.DB(), nil, nil) // Create a second sm and a move only on that sm order2 := factory.BuildOrder(suite.DB(), nil, nil) moveOptions := m.MoveOptions{ - Show: m.BoolPointer(true), + Show: m.BoolPointer(true), + CounselingOfficeID: &office.ID, } move2, verrs, err := order2.CreateNewMove(suite.DB(), moveOptions) suite.NoError(err) @@ -213,9 +219,11 @@ func (suite *ModelSuite) TestSaveMoveDependenciesFail() { orders := factory.BuildOrder(suite.DB(), nil, nil) orders.Status = "" factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) + office := factory.BuildTransportationOffice(suite.DB(), nil, nil) moveOptions := m.MoveOptions{ - Show: m.BoolPointer(true), + Show: m.BoolPointer(true), + CounselingOfficeID: &office.ID, } move, verrs, err := orders.CreateNewMove(suite.DB(), moveOptions) suite.NoError(err) @@ -224,6 +232,7 @@ func (suite *ModelSuite) TestSaveMoveDependenciesFail() { move.Orders = orders verrs, _ = m.SaveMoveDependencies(suite.DB(), move) + suite.True(verrs.HasAny(), "saving invalid statuses should yield an error") } @@ -232,9 +241,11 @@ func (suite *ModelSuite) TestSaveMoveDependenciesSuccess() { orders := factory.BuildOrder(suite.DB(), nil, nil) orders.Status = m.OrderStatusSUBMITTED factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) + office := factory.BuildTransportationOffice(suite.DB(), nil, nil) moveOptions := m.MoveOptions{ - Show: m.BoolPointer(true), + Show: m.BoolPointer(true), + CounselingOfficeID: &office.ID, } move, verrs, err := orders.CreateNewMove(suite.DB(), moveOptions) suite.NoError(err) diff --git a/pkg/models/ppm_shipment.go b/pkg/models/ppm_shipment.go index 629dab0f0fb..6c2cc9272d9 100644 --- a/pkg/models/ppm_shipment.go +++ b/pkg/models/ppm_shipment.go @@ -72,8 +72,8 @@ type PPMSITEstimatedCostInfo struct { type PPMShipmentStatus string const ( - // PPMShipmentStatusCancelled captures enum value "DRAFT" - PPMShipmentStatusCancelled PPMShipmentStatus = "CANCELLED" + // PPMShipmentStatusCanceled captures enum value "CANCELED" + PPMShipmentStatusCanceled PPMShipmentStatus = "CANCELED" // PPMShipmentStatusDraft captures enum value "DRAFT" PPMShipmentStatusDraft PPMShipmentStatus = "DRAFT" // PPMShipmentStatusSubmitted captures enum value "SUBMITTED" @@ -93,7 +93,7 @@ const ( // AllowedPPMShipmentStatuses is a list of all the allowed values for the Status of a PPMShipment as strings. Needed for // validation. var AllowedPPMShipmentStatuses = []string{ - string(PPMShipmentStatusCancelled), + string(PPMShipmentStatusCanceled), string(PPMShipmentStatusDraft), string(PPMShipmentStatusSubmitted), string(PPMShipmentStatusWaitingOnCustomer), @@ -244,7 +244,7 @@ func (p PPMShipment) TableName() string { // Cancel marks the PPM as Canceled func (p *PPMShipment) CancelShipment() error { - p.Status = PPMShipmentStatusCancelled + p.Status = PPMShipmentStatusCanceled return nil } diff --git a/pkg/models/queue_test.go b/pkg/models/queue_test.go index d86aec5c3aa..18d2e3b90c0 100644 --- a/pkg/models/queue_test.go +++ b/pkg/models/queue_test.go @@ -33,9 +33,11 @@ func (suite *ModelSuite) TestCreateMoveWithPPMNoShow() { func (suite *ModelSuite) TestCreateNewMoveWithNoPPMShow() { orders := factory.BuildOrder(suite.DB(), nil, nil) factory.FetchOrBuildDefaultContractor(suite.DB(), nil, nil) + office := factory.BuildTransportationOffice(suite.DB(), nil, nil) moveOptions := m.MoveOptions{ - Show: m.BoolPointer(true), + Show: m.BoolPointer(true), + CounselingOfficeID: &office.ID, } _, verrs, err := orders.CreateNewMove(suite.DB(), moveOptions) suite.NoError(err) diff --git a/pkg/models/service_item_param_key.go b/pkg/models/service_item_param_key.go index 6a708cb8f94..3bfd789dcc4 100644 --- a/pkg/models/service_item_param_key.go +++ b/pkg/models/service_item_param_key.go @@ -155,6 +155,8 @@ const ( ServiceItemParamNameStandaloneCrateCap ServiceItemParamName = "StandaloneCrateCap" // ServiceItemParamNameUncappedRequestTotal is the param key name UncappedRequestTotal ServiceItemParamNameUncappedRequestTotal ServiceItemParamName = "UncappedRequestTotal" + // ServiceItemParamNameLockedPriceCents is the param key name LockedPriceCents + ServiceItemParamNameLockedPriceCents ServiceItemParamName = "LockedPriceCents" ) // ServiceItemParamType is a type of service item parameter @@ -272,6 +274,7 @@ var ValidServiceItemParamNames = []ServiceItemParamName{ ServiceItemParamNameStandaloneCrate, ServiceItemParamNameStandaloneCrateCap, ServiceItemParamNameUncappedRequestTotal, + ServiceItemParamNameLockedPriceCents, } // ValidServiceItemParamNameStrings lists all valid service item param key names @@ -345,6 +348,7 @@ var ValidServiceItemParamNameStrings = []string{ string(ServiceItemParamNameStandaloneCrate), string(ServiceItemParamNameStandaloneCrateCap), string(ServiceItemParamNameUncappedRequestTotal), + string(ServiceItemParamNameLockedPriceCents), } // ValidServiceItemParamTypes lists all valid service item param types diff --git a/pkg/paperwork/generator.go b/pkg/paperwork/generator.go index 881f58c454a..23ee0accdc3 100644 --- a/pkg/paperwork/generator.go +++ b/pkg/paperwork/generator.go @@ -213,7 +213,7 @@ func (g *Generator) GetPdfFileInfoByContents(file afero.File) (*pdfcpu.PDFInfo, // CreateMergedPDFUpload converts Uploads to PDF and merges them into a single PDF func (g *Generator) CreateMergedPDFUpload(appCtx appcontext.AppContext, uploads models.Uploads) (afero.File, error) { - pdfs, err := g.ConvertUploadsToPDF(appCtx, uploads) + pdfs, err := g.ConvertUploadsToPDF(appCtx, uploads, true) if err != nil { return nil, errors.Wrap(err, "Error while converting uploads") } @@ -227,7 +227,7 @@ func (g *Generator) CreateMergedPDFUpload(appCtx appcontext.AppContext, uploads } // ConvertUploadsToPDF turns a slice of Uploads into a slice of paths to converted PDF files -func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads models.Uploads) ([]string, error) { +func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads models.Uploads, doRotation bool) ([]string, error) { // tempfile paths to be returned pdfs := make([]string, 0) @@ -240,9 +240,18 @@ func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads mo if len(images) > 0 { // We want to retain page order and will generate a PDF for images // that have already been encountered before handling this PDF. - pdf, err := g.PDFFromImages(appCtx, images) - if err != nil { - return nil, errors.Wrap(err, "Converting images") + var pdf string + var err error + if doRotation { + pdf, err = g.PDFFromImages(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting images") + } + } else { + pdf, err = g.PDFFromImagesNoRotation(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting images") + } } pdfs = append(pdfs, pdf) images = make([]inputFile, 0) @@ -282,9 +291,19 @@ func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads mo // Merge all remaining images in urls into a new PDF if len(images) > 0 { - pdf, err := g.PDFFromImages(appCtx, images) - if err != nil { - return nil, errors.Wrap(err, "Converting remaining images to pdf") + var pdf string + var err error + + if doRotation { + pdf, err = g.PDFFromImages(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting remaining images to pdf") + } + } else { + pdf, err = g.PDFFromImagesNoRotation(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting remaining images to pdf") + } } pdfs = append(pdfs, pdf) } @@ -514,6 +533,115 @@ func (g *Generator) PDFFromImages(appCtx appcontext.AppContext, images []inputFi return outputFile.Name(), nil } +// PDFFromImages returns the path to tempfile PDF containing all images included +// in urls. +// +// The files at those paths will be tempfiles that will need to be cleaned +// up by the caller. +func (g *Generator) PDFFromImagesNoRotation(appCtx appcontext.AppContext, images []inputFile) (string, error) { + // These constants are based on A4 page size, which we currently default to. + horizontalMargin := 0.0 + topMargin := 0.0 + bodyWidth := PdfPageWidth - (horizontalMargin * 2) + bodyHeight := PdfPageHeight - (topMargin * 2) + wToHRatio := bodyWidth / bodyHeight + + pdf := gofpdf.New(PdfOrientation, PdfUnit, PdfPageSize, PdfFontDir) + pdf.SetMargins(horizontalMargin, topMargin, horizontalMargin) + + if len(images) == 0 { + return "", errors.New("No images provided") + } + + appCtx.Logger().Debug("generating PDF from image files", zap.Any("images", images)) + + outputFile, err := g.newTempFile() + if err != nil { + return "", err + } + + defer func() { + if closeErr := outputFile.Close(); closeErr != nil { + appCtx.Logger().Debug("Failed to close file", zap.Error(closeErr)) + } + }() + + var opt gofpdf.ImageOptions + for _, img := range images { + pdf.AddPage() + file, openErr := g.fs.Open(img.Path) + if openErr != nil { + return "", errors.Wrap(openErr, "Opening image file") + } + + defer func() { + if closeErr := file.Close(); closeErr != nil { + appCtx.Logger().Debug("Failed to close file", zap.Error(closeErr)) + } + }() + + if img.ContentType == uploader.FileTypePNG { + appCtx.Logger().Debug("Converting png to 8-bit") + // gofpdf isn't able to process 16-bit PNGs, so to be safe we convert all PNGs to an 8-bit color depth + newFile, newTemplateFileErr := g.newTempFile() + if newTemplateFileErr != nil { + return "", errors.Wrap(newTemplateFileErr, "Creating temp file for png conversion") + } + + defer func() { + if closeErr := newFile.Close(); closeErr != nil { + appCtx.Logger().Debug("Failed to close file", zap.Error(closeErr)) + } + }() + + convertTo8BitPNGErr := convertTo8BitPNG(file, newFile) + if convertTo8BitPNGErr != nil { + return "", errors.Wrap(convertTo8BitPNGErr, "Converting to 8-bit png") + } + file = newFile + _, fileSeekErr := file.Seek(0, io.SeekStart) + if fileSeekErr != nil { + return "", errors.Wrapf(fileSeekErr, "file.Seek offset: 0 whence: %d", io.SeekStart) + } + } + + widthInPdf := bodyWidth + heightInPdf := 0.0 + + // Scale using the imageOptions below + // BodyWidth should be set to 0 when the image height the proportion of the page + // is taller than wide as compared to an A4 page. + // + // The opposite is true and defaulted for when the image is wider than it is tall, + // in comparison to an A4 page. + if float64(bodyWidth/bodyHeight) < wToHRatio { + widthInPdf = 0 + heightInPdf = bodyHeight + } + + // Seek to the beginning of the file so when we register the image, it doesn't start + // at the end of the file. + _, fileSeekErr := file.Seek(0, io.SeekStart) + if fileSeekErr != nil { + return "", errors.Wrapf(fileSeekErr, "file.Seek offset: 0 whence: %d", io.SeekStart) + } + // Need to register the image using an afero reader, else it uses default filesystem + pdf.RegisterImageReader(img.Path, contentTypeToImageType[img.ContentType], file) + opt.ImageType = contentTypeToImageType[img.ContentType] + + pdf.ImageOptions(img.Path, horizontalMargin, topMargin, widthInPdf, heightInPdf, false, opt, 0, "") + fileCloseErr := file.Close() + if fileCloseErr != nil { + return "", errors.Wrapf(err, "error closing file: %s", file.Name()) + } + } + + if err = pdf.OutputAndClose(outputFile); err != nil { + return "", errors.Wrap(err, "could not write PDF to outputfile") + } + return outputFile.Name(), nil +} + // MergePDFFiles Merges a slice of paths to PDF files into a single PDF func (g *Generator) MergePDFFiles(_ appcontext.AppContext, paths []string) (afero.File, error) { var err error diff --git a/pkg/paperwork/generator_test.go b/pkg/paperwork/generator_test.go index 8b0eddea933..775c3207696 100644 --- a/pkg/paperwork/generator_test.go +++ b/pkg/paperwork/generator_test.go @@ -143,6 +143,65 @@ func (suite *PaperworkSuite) TestPDFFromImages() { suite.Contains(checksums, orders2Checksum, "did not find hash for orders2.jpg") } +func (suite *PaperworkSuite) TestPDFFromImagesNoRotation() { + generator, newGeneratorErr := NewGenerator(suite.userUploader.Uploader()) + suite.FatalNil(newGeneratorErr) + + images := []inputFile{ + {Path: "testdata/orders1.jpg", ContentType: uploader.FileTypeJPEG}, + {Path: "testdata/orders2.jpg", ContentType: uploader.FileTypeJPEG}, + } + for _, image := range images { + _, err := suite.openLocalFile(image.Path, generator.fs) + suite.FatalNil(err) + } + + generatedPath, err := generator.PDFFromImagesNoRotation(suite.AppContextForTest(), images) + suite.FatalNil(err, "failed to generate pdf") + aferoFile, err := generator.fs.Open(generatedPath) + suite.FatalNil(err, "afero failed to open pdf") + + suite.NotEmpty(generatedPath, "got an empty path to the generated file") + suite.FatalNil(err) + + // verify that the images are in the pdf by extracting them and checking their checksums + file, err := afero.ReadAll(aferoFile) + suite.FatalNil(err) + tmpDir, err := os.MkdirTemp("", "images") + suite.FatalNil(err) + f, err := os.CreateTemp(tmpDir, "") + suite.FatalNil(err) + err = os.WriteFile(f.Name(), file, os.ModePerm) + suite.FatalNil(err) + err = api.ExtractImagesFile(f.Name(), tmpDir, []string{"-2"}, generator.pdfConfig) + suite.FatalNil(err) + err = os.Remove(f.Name()) + suite.FatalNil(err) + + checksums := make([]string, 2) + files, err := os.ReadDir(tmpDir) + suite.FatalNil(err) + + suite.Equal(4, len(files), "did not find 2 images") + + for _, file := range files { + checksum, sha256ForPathErr := suite.sha256ForPath(path.Join(tmpDir, file.Name()), nil) + suite.FatalNil(sha256ForPathErr, "error calculating hash") + if sha256ForPathErr != nil { + suite.FailNow(sha256ForPathErr.Error()) + } + checksums = append(checksums, checksum) + } + + orders1Checksum, err := suite.sha256ForPath("testdata/orders1.jpg", generator.fs) + suite.Nil(err, "error calculating hash") + suite.Contains(checksums, orders1Checksum, "did not find hash for orders1.jpg") + + orders2Checksum, err := suite.sha256ForPath("testdata/orders2.jpg", generator.fs) + suite.Nil(err, "error calculating hash") + suite.Contains(checksums, orders2Checksum, "did not find hash for orders2.jpg") +} + func (suite *PaperworkSuite) TestPDFFromImages16BitPNG() { generator, err := NewGenerator(suite.userUploader.Uploader()) suite.FatalNil(err) @@ -187,7 +246,7 @@ func (suite *PaperworkSuite) TestGenerateUploadsPDF() { uploads, err := models.UploadsFromUserUploads(suite.DB(), order.UploadedOrders.UserUploads) suite.FatalNil(err) - paths, err := generator.ConvertUploadsToPDF(suite.AppContextForTest(), uploads) + paths, err := generator.ConvertUploadsToPDF(suite.AppContextForTest(), uploads, true) suite.FatalNil(err) suite.Equal(3, len(paths), "wrong number of paths returned") diff --git a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go index ca8d76e8881..d901bdf58cd 100644 --- a/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go +++ b/pkg/payment_request/service_param_value_lookups/service_param_value_lookups.go @@ -84,6 +84,7 @@ var ServiceItemParamsWithLookups = []models.ServiceItemParamName{ models.ServiceItemParamNameDimensionWidth, models.ServiceItemParamNameStandaloneCrate, models.ServiceItemParamNameStandaloneCrateCap, + models.ServiceItemParamNameLockedPriceCents, } // ServiceParamLookupInitialize initializes service parameter lookup @@ -425,6 +426,10 @@ func InitializeLookups(appCtx appcontext.AppContext, shipment models.MTOShipment ServiceItem: serviceItem, } + lookups[models.ServiceItemParamNameLockedPriceCents] = LockedPriceCentsLookup{ + ServiceItem: serviceItem, + } + return lookups } diff --git a/pkg/services/ghc_rate_engine.go b/pkg/services/ghc_rate_engine.go index 51bd1d29d5e..5d17e0388ce 100644 --- a/pkg/services/ghc_rate_engine.go +++ b/pkg/services/ghc_rate_engine.go @@ -33,7 +33,7 @@ type ParamsPricer interface { // //go:generate mockery --name ManagementServicesPricer type ManagementServicesPricer interface { - Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, PricingDisplayParams, error) ParamsPricer } @@ -41,7 +41,7 @@ type ManagementServicesPricer interface { // //go:generate mockery --name CounselingServicesPricer type CounselingServicesPricer interface { - Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, PricingDisplayParams, error) + Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, PricingDisplayParams, error) ParamsPricer } diff --git a/pkg/services/ghcrateengine/counseling_services_pricer.go b/pkg/services/ghcrateengine/counseling_services_pricer.go index e2c3869474f..f695e9753c3 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer.go @@ -2,7 +2,6 @@ package ghcrateengine import ( "fmt" - "time" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" @@ -19,32 +18,30 @@ func NewCounselingServicesPricer() services.CounselingServicesPricer { } // Price determines the price for a counseling service -func (p counselingServicesPricer) Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, services.PricingDisplayParams, error) { - taskOrderFee, err := fetchTaskOrderFee(appCtx, contractCode, models.ReServiceCodeCS, mtoAvailableToPrimeAt) - if err != nil { - return unit.Cents(0), nil, fmt.Errorf("could not fetch task order fee: %w", err) +func (p counselingServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { + + if lockedPriceCents == nil { + return 0, nil, fmt.Errorf("invalid value for locked_price_cents") } - displayPriceParams := services.PricingDisplayParams{ + params := services.PricingDisplayParams{ { Key: models.ServiceItemParamNamePriceRateOrFactor, - Value: FormatCents(taskOrderFee.PriceCents), + Value: FormatCents(*lockedPriceCents), }, } - return taskOrderFee.PriceCents, displayPriceParams, nil + + return *lockedPriceCents, params, nil } // PriceUsingParams determines the price for a counseling service given PaymentServiceItemParams func (p counselingServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { - contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) - if err != nil { - return unit.Cents(0), nil, err - } - mtoAvailableToPrimeAt, err := getParamTime(params, models.ServiceItemParamNameMTOAvailableToPrimeAt) + lockedPriceCents, err := getParamInt(params, models.ServiceItemParamNameLockedPriceCents) if err != nil { return unit.Cents(0), nil, err } - return p.Price(appCtx, contractCode, mtoAvailableToPrimeAt) + lockedPrice := unit.Cents(lockedPriceCents) + return p.Price(appCtx, &lockedPrice) } diff --git a/pkg/services/ghcrateengine/counseling_services_pricer_test.go b/pkg/services/ghcrateengine/counseling_services_pricer_test.go index e59368337ac..258ee8ce40a 100644 --- a/pkg/services/ghcrateengine/counseling_services_pricer_test.go +++ b/pkg/services/ghcrateengine/counseling_services_pricer_test.go @@ -1,26 +1,21 @@ package ghcrateengine import ( - "time" - "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" - "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/unit" ) const ( - csPriceCents = unit.Cents(8327) + csPriceCents = unit.Cents(12303) ) -var csAvailableToPrimeAt = time.Date(testdatagen.TestYear, time.June, 5, 7, 33, 11, 456, time.UTC) - func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { + lockedPrice := csPriceCents counselingServicesPricer := NewCounselingServicesPricer() suite.Run("success using PaymentServiceItemParams", func() { - suite.setupTaskOrderFeeData(models.ReServiceCodeCS, csPriceCents) paymentServiceItem := suite.setupCounselingServicesItem() priceCents, displayParams, err := counselingServicesPricer.PriceUsingParams(suite.AppContextForTest(), paymentServiceItem.PaymentServiceItemParams) @@ -37,7 +32,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { suite.Run("success without PaymentServiceItemParams", func() { suite.setupTaskOrderFeeData(models.ReServiceCodeCS, csPriceCents) - priceCents, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, csAvailableToPrimeAt) + priceCents, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), &lockedPrice) suite.NoError(err) suite.Equal(csPriceCents, priceCents) }) @@ -48,11 +43,6 @@ func (suite *GHCRateEngineServiceSuite) TestPriceCounselingServices() { _, _, err := counselingServicesPricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) suite.Error(err) }) - - suite.Run("not finding a rate record", func() { - _, _, err := counselingServicesPricer.Price(suite.AppContextForTest(), "BOGUS", csAvailableToPrimeAt) - suite.Error(err) - }) } func (suite *GHCRateEngineServiceSuite) setupCounselingServicesItem() models.PaymentServiceItem { @@ -61,14 +51,9 @@ func (suite *GHCRateEngineServiceSuite) setupCounselingServicesItem() models.Pay models.ReServiceCodeCS, []factory.CreatePaymentServiceItemParams{ { - Key: models.ServiceItemParamNameContractCode, - KeyType: models.ServiceItemParamTypeString, - Value: factory.DefaultContractCode, - }, - { - Key: models.ServiceItemParamNameMTOAvailableToPrimeAt, - KeyType: models.ServiceItemParamTypeTimestamp, - Value: csAvailableToPrimeAt.Format(TimestampParamFormat), + Key: models.ServiceItemParamNameLockedPriceCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: csPriceCents.ToMillicents().ToCents().String(), }, }, nil, nil, ) diff --git a/pkg/services/ghcrateengine/management_services_pricer.go b/pkg/services/ghcrateengine/management_services_pricer.go index f01c990a1de..995003c196b 100644 --- a/pkg/services/ghcrateengine/management_services_pricer.go +++ b/pkg/services/ghcrateengine/management_services_pricer.go @@ -2,7 +2,6 @@ package ghcrateengine import ( "fmt" - "time" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" @@ -19,32 +18,30 @@ func NewManagementServicesPricer() services.ManagementServicesPricer { } // Price determines the price for a management service -func (p managementServicesPricer) Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, services.PricingDisplayParams, error) { - taskOrderFee, err := fetchTaskOrderFee(appCtx, contractCode, models.ReServiceCodeMS, mtoAvailableToPrimeAt) - if err != nil { - return unit.Cents(0), nil, fmt.Errorf("could not fetch task order fee: %w", err) +func (p managementServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { + + if lockedPriceCents == nil { + return 0, nil, fmt.Errorf("invalid value for locked_price_cents") } + params := services.PricingDisplayParams{ { Key: models.ServiceItemParamNamePriceRateOrFactor, - Value: FormatCents(taskOrderFee.PriceCents), + Value: FormatCents(*lockedPriceCents), }, } - return taskOrderFee.PriceCents, params, nil + return *lockedPriceCents, params, nil } // PriceUsingParams determines the price for a management service given PaymentServiceItemParams func (p managementServicesPricer) PriceUsingParams(appCtx appcontext.AppContext, params models.PaymentServiceItemParams) (unit.Cents, services.PricingDisplayParams, error) { - contractCode, err := getParamString(params, models.ServiceItemParamNameContractCode) - if err != nil { - return unit.Cents(0), nil, err - } - mtoAvailableToPrimeAt, err := getParamTime(params, models.ServiceItemParamNameMTOAvailableToPrimeAt) + lockedPriceCents, err := getParamInt(params, models.ServiceItemParamNameLockedPriceCents) if err != nil { return unit.Cents(0), nil, err } - return p.Price(appCtx, contractCode, mtoAvailableToPrimeAt) + lockedPrice := unit.Cents(lockedPriceCents) + return p.Price(appCtx, &lockedPrice) } diff --git a/pkg/services/ghcrateengine/management_services_pricer_test.go b/pkg/services/ghcrateengine/management_services_pricer_test.go index 52355a2695b..01452e741c4 100644 --- a/pkg/services/ghcrateengine/management_services_pricer_test.go +++ b/pkg/services/ghcrateengine/management_services_pricer_test.go @@ -1,12 +1,9 @@ package ghcrateengine import ( - "time" - "github.com/transcom/mymove/pkg/factory" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/services" - "github.com/transcom/mymove/pkg/testdatagen" "github.com/transcom/mymove/pkg/unit" ) @@ -14,9 +11,8 @@ const ( msPriceCents = unit.Cents(12303) ) -var msAvailableToPrimeAt = time.Date(testdatagen.TestYear, time.June, 3, 12, 57, 33, 123, time.UTC) - func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { + lockedPrice := csPriceCents suite.Run("success using PaymentServiceItemParams", func() { suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) paymentServiceItem := suite.setupManagementServicesItem() @@ -37,7 +33,7 @@ func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) managementServicesPricer := NewManagementServicesPricer() - priceCents, _, err := managementServicesPricer.Price(suite.AppContextForTest(), testdatagen.DefaultContractCode, msAvailableToPrimeAt) + priceCents, _, err := managementServicesPricer.Price(suite.AppContextForTest(), &lockedPrice) suite.NoError(err) suite.Equal(msPriceCents, priceCents) }) @@ -49,14 +45,6 @@ func (suite *GHCRateEngineServiceSuite) TestPriceManagementServices() { _, _, err := managementServicesPricer.PriceUsingParams(suite.AppContextForTest(), models.PaymentServiceItemParams{}) suite.Error(err) }) - - suite.Run("not finding a rate record", func() { - suite.setupTaskOrderFeeData(models.ReServiceCodeMS, msPriceCents) - managementServicesPricer := NewManagementServicesPricer() - - _, _, err := managementServicesPricer.Price(suite.AppContextForTest(), "BOGUS", msAvailableToPrimeAt) - suite.Error(err) - }) } func (suite *GHCRateEngineServiceSuite) setupManagementServicesItem() models.PaymentServiceItem { @@ -65,14 +53,9 @@ func (suite *GHCRateEngineServiceSuite) setupManagementServicesItem() models.Pay models.ReServiceCodeMS, []factory.CreatePaymentServiceItemParams{ { - Key: models.ServiceItemParamNameContractCode, - KeyType: models.ServiceItemParamTypeString, - Value: factory.DefaultContractCode, - }, - { - Key: models.ServiceItemParamNameMTOAvailableToPrimeAt, - KeyType: models.ServiceItemParamTypeTimestamp, - Value: msAvailableToPrimeAt.Format(TimestampParamFormat), + Key: models.ServiceItemParamNameLockedPriceCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: msPriceCents.ToMillicents().ToCents().String(), }, }, nil, nil, ) diff --git a/pkg/services/ghcrateengine/service_item_pricer_test.go b/pkg/services/ghcrateengine/service_item_pricer_test.go index ca9ae0cb724..6ebfec34a29 100644 --- a/pkg/services/ghcrateengine/service_item_pricer_test.go +++ b/pkg/services/ghcrateengine/service_item_pricer_test.go @@ -115,14 +115,9 @@ func (suite *GHCRateEngineServiceSuite) setupPriceServiceItem() models.PaymentSe models.ReServiceCodeMS, []factory.CreatePaymentServiceItemParams{ { - Key: models.ServiceItemParamNameContractCode, - KeyType: models.ServiceItemParamTypeString, - Value: factory.DefaultContractCode, - }, - { - Key: models.ServiceItemParamNameMTOAvailableToPrimeAt, - KeyType: models.ServiceItemParamTypeTimestamp, - Value: msAvailableToPrimeAt.Format(TimestampParamFormat), + Key: models.ServiceItemParamNameLockedPriceCents, + KeyType: models.ServiceItemParamTypeInteger, + Value: msPriceCents.ToMillicents().ToCents().String(), }, }, nil, nil, ) diff --git a/pkg/services/invoice/process_edi997.go b/pkg/services/invoice/process_edi997.go index c67fbc1227b..27f65603b2a 100644 --- a/pkg/services/invoice/process_edi997.go +++ b/pkg/services/invoice/process_edi997.go @@ -2,6 +2,7 @@ package invoice import ( "fmt" + "time" "github.com/gofrs/uuid" "go.uber.org/zap" @@ -105,6 +106,8 @@ func (e *edi997Processor) ProcessFile(appCtx appcontext.AppContext, _ string, st } paymentRequest.Status = models.PaymentRequestStatusTppsReceived + ReceivedByGexAt := time.Now() + paymentRequest.ReceivedByGexAt = &ReceivedByGexAt err = txnAppCtx.DB().Update(&paymentRequest) if err != nil { txnAppCtx.Logger().Error("failure updating payment request", zap.Error(err)) diff --git a/pkg/services/invoice/process_edi997_test.go b/pkg/services/invoice/process_edi997_test.go index 7287b043c7f..39d4c34cba3 100644 --- a/pkg/services/invoice/process_edi997_test.go +++ b/pkg/services/invoice/process_edi997_test.go @@ -196,6 +196,7 @@ IEA*1*000000995 err = suite.DB().Where("id = ?", paymentRequest.ID).First(&updatedPR) suite.NoError(err) suite.Equal(models.PaymentRequestStatusTppsReceived, updatedPR.Status) + suite.NotNil(updatedPR.ReceivedByGexAt) }) suite.Run("can handle 997 and 858 with same ICN", func() { @@ -250,6 +251,7 @@ IEA*1*000000995 err = suite.DB().Where("id = ?", paymentRequest.ID).First(&updatedPR) suite.FatalNoError(err) suite.Equal(models.PaymentRequestStatusTppsReceived, updatedPR.Status) + suite.NotNil(updatedPR.ReceivedByGexAt) }) suite.Run("does not error out if edi with same icn is processed for the same payment request", func() { @@ -304,6 +306,7 @@ IEA*1*000000995 err = suite.DB().Where("id = ?", paymentRequest.ID).First(&updatedPR) suite.FatalNoError(err) suite.Equal(models.PaymentRequestStatusTppsReceived, updatedPR.Status) + suite.NotNil(updatedPR.ReceivedByGexAt) }) suite.Run("doesn't update a payment request status after processing an invalid EDI997", func() { @@ -345,6 +348,7 @@ IEA*1*000000022 err = suite.DB().Where("id = ?", paymentRequest.ID).First(&updatedPR) suite.NoError(err) suite.Equal(models.PaymentRequestStatusSentToGex, updatedPR.Status) + suite.Nil(updatedPR.ReceivedByGexAt) }) suite.Run("throw an error when edi997 is missing a transaction set", func() { diff --git a/pkg/services/mocks/CounselingServicesPricer.go b/pkg/services/mocks/CounselingServicesPricer.go index 561fcec66c7..1b6400c5a1b 100644 --- a/pkg/services/mocks/CounselingServicesPricer.go +++ b/pkg/services/mocks/CounselingServicesPricer.go @@ -10,8 +10,6 @@ import ( services "github.com/transcom/mymove/pkg/services" - time "time" - unit "github.com/transcom/mymove/pkg/unit" ) @@ -20,32 +18,32 @@ type CounselingServicesPricer struct { mock.Mock } -// Price provides a mock function with given fields: appCtx, contractCode, mtoAvailableToPrimeAt -func (_m *CounselingServicesPricer) Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, services.PricingDisplayParams, error) { - ret := _m.Called(appCtx, contractCode, mtoAvailableToPrimeAt) +// Price provides a mock function with given fields: appCtx, lockedPriceCents +func (_m *CounselingServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, lockedPriceCents) var r0 unit.Cents var r1 services.PricingDisplayParams var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time) (unit.Cents, services.PricingDisplayParams, error)); ok { - return rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *unit.Cents) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, lockedPriceCents) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time) unit.Cents); ok { - r0 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *unit.Cents) unit.Cents); ok { + r0 = rf(appCtx, lockedPriceCents) } else { r0 = ret.Get(0).(unit.Cents) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time) services.PricingDisplayParams); ok { - r1 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *unit.Cents) services.PricingDisplayParams); ok { + r1 = rf(appCtx, lockedPriceCents) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(services.PricingDisplayParams) } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time) error); ok { - r2 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(2).(func(appcontext.AppContext, *unit.Cents) error); ok { + r2 = rf(appCtx, lockedPriceCents) } else { r2 = ret.Error(2) } diff --git a/pkg/services/mocks/ManagementServicesPricer.go b/pkg/services/mocks/ManagementServicesPricer.go index 97df59714cd..4a832d51602 100644 --- a/pkg/services/mocks/ManagementServicesPricer.go +++ b/pkg/services/mocks/ManagementServicesPricer.go @@ -10,8 +10,6 @@ import ( services "github.com/transcom/mymove/pkg/services" - time "time" - unit "github.com/transcom/mymove/pkg/unit" ) @@ -20,32 +18,32 @@ type ManagementServicesPricer struct { mock.Mock } -// Price provides a mock function with given fields: appCtx, contractCode, mtoAvailableToPrimeAt -func (_m *ManagementServicesPricer) Price(appCtx appcontext.AppContext, contractCode string, mtoAvailableToPrimeAt time.Time) (unit.Cents, services.PricingDisplayParams, error) { - ret := _m.Called(appCtx, contractCode, mtoAvailableToPrimeAt) +// Price provides a mock function with given fields: appCtx, lockedPriceCents +func (_m *ManagementServicesPricer) Price(appCtx appcontext.AppContext, lockedPriceCents *unit.Cents) (unit.Cents, services.PricingDisplayParams, error) { + ret := _m.Called(appCtx, lockedPriceCents) var r0 unit.Cents var r1 services.PricingDisplayParams var r2 error - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time) (unit.Cents, services.PricingDisplayParams, error)); ok { - return rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *unit.Cents) (unit.Cents, services.PricingDisplayParams, error)); ok { + return rf(appCtx, lockedPriceCents) } - if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time) unit.Cents); ok { - r0 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(0).(func(appcontext.AppContext, *unit.Cents) unit.Cents); ok { + r0 = rf(appCtx, lockedPriceCents) } else { r0 = ret.Get(0).(unit.Cents) } - if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time) services.PricingDisplayParams); ok { - r1 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(1).(func(appcontext.AppContext, *unit.Cents) services.PricingDisplayParams); ok { + r1 = rf(appCtx, lockedPriceCents) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(services.PricingDisplayParams) } } - if rf, ok := ret.Get(2).(func(appcontext.AppContext, string, time.Time) error); ok { - r2 = rf(appCtx, contractCode, mtoAvailableToPrimeAt) + if rf, ok := ret.Get(2).(func(appcontext.AppContext, *unit.Cents) error); ok { + r2 = rf(appCtx, lockedPriceCents) } else { r2 = ret.Error(2) } diff --git a/pkg/services/move.go b/pkg/services/move.go index bc3c5b5e54c..99b9f3a775f 100644 --- a/pkg/services/move.go +++ b/pkg/services/move.go @@ -116,6 +116,9 @@ type MoveCloseoutOfficeUpdater interface { UpdateCloseoutOffice(appCtx appcontext.AppContext, moveLocator string, closeoutOfficeID uuid.UUID, eTag string) (*models.Move, error) } +type MoveCanceler interface { + CancelMove(appCtx appcontext.AppContext, moveID uuid.UUID) (*models.Move, error) +} type MoveAssignedOfficeUserUpdater interface { UpdateAssignedOfficeUser(appCtx appcontext.AppContext, moveID uuid.UUID, officeUser *models.OfficeUser, role roles.RoleType) (*models.Move, error) DeleteAssignedOfficeUser(appCtx appcontext.AppContext, moveID uuid.UUID, role roles.RoleType) (*models.Move, error) diff --git a/pkg/services/move/move_router_test.go b/pkg/services/move/move_router_test.go index dfa830bef13..cd6b9f6623e 100644 --- a/pkg/services/move/move_router_test.go +++ b/pkg/services/move/move_router_test.go @@ -433,7 +433,7 @@ func (suite *MoveServiceSuite) TestMoveCancellation() { suite.NoError(err) suite.Equal(models.MoveStatusCANCELED, move.Status, "expected Canceled") - suite.Equal(models.PPMShipmentStatusCancelled, move.MTOShipments[0].PPMShipment.Status, "expected Canceled") + suite.Equal(models.PPMShipmentStatusCanceled, move.MTOShipments[0].PPMShipment.Status, "expected Canceled") suite.Equal(models.OrderStatusCANCELED, move.Orders.Status, "expected Canceled") }) diff --git a/pkg/services/move_task_order/move_task_order_fetcher.go b/pkg/services/move_task_order/move_task_order_fetcher.go index 34cab4439d5..e2e29cc12b3 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher.go +++ b/pkg/services/move_task_order/move_task_order_fetcher.go @@ -115,6 +115,8 @@ func (f moveTaskOrderFetcher) FetchMoveTaskOrder(appCtx appcontext.AppContext, s "MTOShipments.PickupAddress", "MTOShipments.SecondaryDeliveryAddress", "MTOShipments.SecondaryPickupAddress", + "MTOShipments.TertiaryDeliveryAddress", + "MTOShipments.TertiaryPickupAddress", "MTOShipments.MTOAgents", "MTOShipments.SITDurationUpdates", "MTOShipments.StorageFacility", diff --git a/pkg/services/move_task_order/move_task_order_fetcher_test.go b/pkg/services/move_task_order/move_task_order_fetcher_test.go index c22e5b24791..23ba86f84ac 100644 --- a/pkg/services/move_task_order/move_task_order_fetcher_test.go +++ b/pkg/services/move_task_order/move_task_order_fetcher_test.go @@ -128,6 +128,48 @@ func (suite *MoveTaskOrderServiceSuite) TestMoveTaskOrderFetcher() { suite.Equal(expectedAddressUpdate.OriginalAddress.Country, actualAddressUpdate.OriginalAddress.Country) }) + suite.Run("Success with fetching a MTO with a Shipment Address Update that has a customized Original Address and three addresses", func() { + traits := []factory.Trait{factory.GetTraitShipmentAddressUpdateApproved} + + expectedAddressUpdate := factory.BuildShipmentAddressUpdate(suite.DB(), []factory.Customization{ + { + Model: models.Address{ + StreetAddress1: "123 Main St", + StreetAddress2: models.StringPointer("Apt 2"), + StreetAddress3: models.StringPointer("Suite 200"), + City: "New York", + State: "NY", + PostalCode: "10001", + Country: models.StringPointer("US"), + }, + Type: &factory.Addresses.OriginalAddress, + }, + }, traits) + + searchParams := services.MoveTaskOrderFetcherParams{ + IncludeHidden: false, + IsAvailableToPrime: true, + MoveTaskOrderID: expectedAddressUpdate.Shipment.MoveTaskOrder.ID, + } + + actualMTO, err := mtoFetcher.FetchMoveTaskOrder(suite.AppContextForTest(), &searchParams) + suite.NoError(err) + + actualAddressUpdate := actualMTO.MTOShipments[0].DeliveryAddressUpdate + + // Validate MTO was fetched that includes expected shipment address update with customized original address + suite.Equal(expectedAddressUpdate.ShipmentID, actualAddressUpdate.ShipmentID) + suite.Equal(expectedAddressUpdate.Status, actualAddressUpdate.Status) + suite.ElementsMatch(expectedAddressUpdate.OriginalAddressID, actualAddressUpdate.OriginalAddressID) + suite.Equal(expectedAddressUpdate.OriginalAddress.StreetAddress1, actualAddressUpdate.OriginalAddress.StreetAddress1) + suite.Equal(expectedAddressUpdate.OriginalAddress.StreetAddress2, actualAddressUpdate.OriginalAddress.StreetAddress2) + suite.Equal(expectedAddressUpdate.OriginalAddress.StreetAddress3, actualAddressUpdate.OriginalAddress.StreetAddress3) + suite.Equal(expectedAddressUpdate.OriginalAddress.City, actualAddressUpdate.OriginalAddress.City) + suite.Equal(expectedAddressUpdate.OriginalAddress.State, actualAddressUpdate.OriginalAddress.State) + suite.Equal(expectedAddressUpdate.OriginalAddress.PostalCode, actualAddressUpdate.OriginalAddress.PostalCode) + suite.Equal(expectedAddressUpdate.OriginalAddress.Country, actualAddressUpdate.OriginalAddress.Country) + }) + suite.Run("Success with Prime-available move by ID, fetch all non-deleted shipments", func() { expectedMTO, _ := setupTestData() searchParams := services.MoveTaskOrderFetcherParams{ diff --git a/pkg/services/mto_shipment/mto_shipment_address_updater.go b/pkg/services/mto_shipment/mto_shipment_address_updater.go index 1f9ea1d08ba..edab362940f 100644 --- a/pkg/services/mto_shipment/mto_shipment_address_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_address_updater.go @@ -36,6 +36,8 @@ func isAddressOnShipment(address *models.Address, mtoShipment *models.MTOShipmen mtoShipment.DestinationAddressID, mtoShipment.SecondaryDeliveryAddressID, mtoShipment.SecondaryPickupAddressID, + mtoShipment.TertiaryDeliveryAddressID, + mtoShipment.TertiaryPickupAddressID, } for _, id := range addressIDs { diff --git a/pkg/services/mto_shipment/mto_shipment_creator.go b/pkg/services/mto_shipment/mto_shipment_creator.go index e4d03eae8db..aa65f0e6fe0 100644 --- a/pkg/services/mto_shipment/mto_shipment_creator.go +++ b/pkg/services/mto_shipment/mto_shipment_creator.go @@ -196,7 +196,7 @@ func (f mtoShipmentCreator) CreateMTOShipment(appCtx appcontext.AppContext, ship transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { // create pickup and destination addresses - if shipment.PickupAddress != nil { + if shipment.PickupAddress != nil && shipment.ShipmentType != models.MTOShipmentTypeHHGOutOfNTSDom { pickupAddress, errAddress := f.addressCreator.CreateAddress(txnAppCtx, shipment.PickupAddress) if errAddress != nil { return fmt.Errorf("failed to create pickup address %#v %e", verrs, err) @@ -241,7 +241,7 @@ func (f mtoShipmentCreator) CreateMTOShipment(appCtx appcontext.AppContext, ship shipment.TertiaryPickupAddress.County = county } - if shipment.DestinationAddress != nil { + if shipment.DestinationAddress != nil && shipment.ShipmentType != models.MTOShipmentTypeHHGIntoNTSDom { destinationAddress, errAddress := f.addressCreator.CreateAddress(txnAppCtx, shipment.DestinationAddress) if errAddress != nil { return fmt.Errorf("failed to create destination address %#v %e", verrs, err) @@ -301,6 +301,15 @@ func (f mtoShipmentCreator) CreateMTOShipment(appCtx appcontext.AppContext, ship return fmt.Errorf("failed to create storage facility %#v %e", verrs, err) } shipment.StorageFacilityID = &shipment.StorageFacility.ID + + // For NTS-Release set the pick up address to the storage facility + if shipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTSDom { + shipment.PickupAddressID = &shipment.StorageFacility.AddressID + } + // For NTS set the destination address to the storage facility + if shipment.ShipmentType == models.MTOShipmentTypeHHGIntoNTSDom { + shipment.DestinationAddressID = &shipment.StorageFacility.AddressID + } } //assign status to shipment draft by default diff --git a/pkg/services/mto_shipment/mto_shipment_creator_test.go b/pkg/services/mto_shipment/mto_shipment_creator_test.go index f67a79929ec..71d7b813b2a 100644 --- a/pkg/services/mto_shipment/mto_shipment_creator_test.go +++ b/pkg/services/mto_shipment/mto_shipment_creator_test.go @@ -78,7 +78,7 @@ func (suite *MTOShipmentServiceSuite) TestCreateMTOShipment() { }) suite.Run("Test requested pickup date requirement for various shipment types", func() { - subtestData := suite.createSubtestData(nil) + subtestData := suite.createSubtestDataV2(nil) creator := subtestData.shipmentCreator // Default is HHG, but we set it explicitly below via the test cases @@ -105,10 +105,38 @@ func (suite *MTOShipmentServiceSuite) TestCreateMTOShipment() { } for _, testCase := range testCases { - mtoShipmentClear := clearShipmentIDFields(&mtoShipment) - mtoShipmentClear.ShipmentType = testCase.shipmentType - mtoShipmentClear.RequestedPickupDate = testCase.input - _, err := creator.CreateMTOShipment(suite.AppContextForTest(), mtoShipmentClear) + var err error + if testCase.shipmentType == models.MTOShipmentTypeHHGOutOfNTSDom || testCase.shipmentType == models.MTOShipmentTypeHHGIntoNTSDom { + storageFacility := factory.BuildStorageFacility(nil, nil, nil) + storageFacility.ID = uuid.Must(uuid.NewV4()) + + mtoShipmentWithStorageFacility := factory.BuildMTOShipment(nil, []factory.Customization{ + { + Model: subtestData.move, + LinkOnly: true, + }, + { + Model: models.MTOShipment{ + ShipmentType: testCase.shipmentType, + Status: models.MTOShipmentStatusSubmitted, + }, + }, + { + Model: storageFacility, + LinkOnly: true, + }, + }, nil) + + mtoShipmentWithStorageFacilityClear := clearShipmentIDFields(&mtoShipmentWithStorageFacility) + + _, err = creator.CreateMTOShipment(suite.AppContextForTest(), mtoShipmentWithStorageFacilityClear) + } else { + mtoShipmentClear := clearShipmentIDFields(&mtoShipment) + mtoShipmentClear.ShipmentType = testCase.shipmentType + mtoShipmentClear.RequestedPickupDate = testCase.input + + _, err = creator.CreateMTOShipment(suite.AppContextForTest(), mtoShipmentClear) + } if testCase.shouldError { if suite.Errorf(err, "should have errored for a %s shipment with requested pickup date set to %s", testCase.shipmentType, testCase.input) { @@ -257,6 +285,7 @@ func (suite *MTOShipmentServiceSuite) TestCreateMTOShipment() { mtoShipmentClear := clearShipmentIDFields(&mtoShipment) mtoShipmentClear.MTOServiceItems = models.MTOServiceItems{} mtoShipmentClear.DestinationAddress = nil + mtoShipmentClear.StorageFacility = nil createdShipment, err := creator.CreateMTOShipment(suite.AppContextForTest(), mtoShipmentClear) diff --git a/pkg/services/mto_shipment/mto_shipment_updater.go b/pkg/services/mto_shipment/mto_shipment_updater.go index dd103049145..0772591f912 100644 --- a/pkg/services/mto_shipment/mto_shipment_updater.go +++ b/pkg/services/mto_shipment/mto_shipment_updater.go @@ -158,11 +158,11 @@ func setNewShipmentFields(appCtx appcontext.AppContext, dbShipment *models.MTOSh dbShipment.NTSRecordedWeight = requestedUpdatedShipment.NTSRecordedWeight } - if requestedUpdatedShipment.PickupAddress != nil { + if requestedUpdatedShipment.PickupAddress != nil && dbShipment.ShipmentType != models.MTOShipmentTypeHHGOutOfNTSDom { dbShipment.PickupAddress = requestedUpdatedShipment.PickupAddress } - if requestedUpdatedShipment.DestinationAddress != nil { + if requestedUpdatedShipment.DestinationAddress != nil && dbShipment.ShipmentType != models.MTOShipmentTypeHHGIntoNTSDom { dbShipment.DestinationAddress = requestedUpdatedShipment.DestinationAddress } @@ -445,7 +445,7 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, // vs "don't touch" the field, so we can't safely reset a nil DestinationAddress to the duty // location address for an HHG like we do in the MTOShipmentCreator now. See MB-15718. - if newShipment.DestinationAddress != nil { + if newShipment.DestinationAddress != nil && newShipment.ShipmentType != models.MTOShipmentTypeHHGIntoNTSDom { // If there is an existing DestinationAddressID associated // with the shipment, grab it. if dbShipment.DestinationAddressID != nil { @@ -480,7 +480,7 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, } - if newShipment.PickupAddress != nil { + if newShipment.PickupAddress != nil && newShipment.ShipmentType != models.MTOShipmentTypeHHGOutOfNTSDom { if dbShipment.PickupAddressID != nil { newShipment.PickupAddress.ID = *dbShipment.PickupAddressID } @@ -680,6 +680,15 @@ func (f *mtoShipmentUpdater) updateShipmentRecord(appCtx appcontext.AppContext, } newShipment.StorageFacilityID = &newShipment.StorageFacility.ID + + // For NTS-Release set the pick up address to the storage facility + if newShipment.ShipmentType == models.MTOShipmentTypeHHGOutOfNTSDom { + newShipment.PickupAddressID = &newShipment.StorageFacility.AddressID + } + // For NTS set the destination address to the storage facility + if newShipment.ShipmentType == models.MTOShipmentTypeHHGIntoNTSDom { + newShipment.DestinationAddressID = &newShipment.StorageFacility.AddressID + } } if len(newShipment.MTOAgents) != 0 { diff --git a/pkg/services/order.go b/pkg/services/order.go index cb78df5bd9d..c3e1730132e 100644 --- a/pkg/services/order.go +++ b/pkg/services/order.go @@ -68,5 +68,6 @@ type ListOrderParams struct { OrderType *string PPMStatus *string ViewAsGBLOC *string - SCAssignedUser *string + CounselingOffice *string + SCAssignedUser *string } diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 15580cd75f3..cc1277ba247 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -118,8 +118,9 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid ppmStatusQuery := ppmStatusFilter(params.PPMStatus) SCAssignedUserQuery := SCAssignedUserFilter(params.SCAssignedUser) sortOrderQuery := sortOrder(params.Sort, params.Order, ppmCloseoutGblocs) + counselingQuery := counselingOfficeFilter(params.CounselingOffice) // Adding to an array so we can iterate over them and apply the filters after the query structure is set below - options := [18]QueryOption{branchQuery, locatorQuery, dodIDQuery, emplidQuery, lastNameQuery, originDutyLocationQuery, destinationDutyLocationQuery, moveStatusQuery, gblocQuery, submittedAtQuery, appearedInTOOAtQuery, requestedMoveDateQuery, ppmTypeQuery, closeoutInitiatedQuery, closeoutLocationQuery, ppmStatusQuery, sortOrderQuery, SCAssignedUserQuery} + options := [19]QueryOption{branchQuery, locatorQuery, dodIDQuery, emplidQuery, lastNameQuery, originDutyLocationQuery, destinationDutyLocationQuery, moveStatusQuery, gblocQuery, submittedAtQuery, appearedInTOOAtQuery, requestedMoveDateQuery, ppmTypeQuery, closeoutInitiatedQuery, closeoutLocationQuery, ppmStatusQuery, sortOrderQuery, SCAssignedUserQuery, counselingQuery} var query *pop.Query if ppmCloseoutGblocs { @@ -158,6 +159,7 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid "MTOShipments.PPMShipment", "CloseoutOffice", "LockedByOfficeUser", + "CounselingOffice", "SCAssignedUser", ).InnerJoin("orders", "orders.id = moves.orders_id"). InnerJoin("service_members", "orders.service_member_id = service_members.id"). @@ -170,6 +172,7 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid LeftJoin("move_to_gbloc", "move_to_gbloc.move_id = moves.id"). LeftJoin("duty_locations as dest_dl", "dest_dl.id = orders.new_duty_location_id"). LeftJoin("office_users", "office_users.id = moves.locked_by"). + LeftJoin("transportation_offices", "moves.counseling_transportation_office_id = transportation_offices.id"). LeftJoin("office_users as assigned_user", "moves.sc_assigned_id = assigned_user.id"). Where("show = ?", models.BoolPointer(true)) @@ -231,6 +234,10 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid if params.Sort != nil && *params.Sort == "ppmStatus" { groupByColumms = append(groupByColumms, "ppm_shipments.id") } + + if params.Sort != nil && *params.Sort == "counselingOffice" { + groupByColumms = append(groupByColumms, "transportation_offices.id") + } if params.Sort != nil && *params.Sort == "assignedTo" { groupByColumms = append(groupByColumms, "assigned_user.last_name", "assigned_user.first_name") } @@ -547,6 +554,14 @@ func destinationDutyLocationFilter(destinationDutyLocation *string) QueryOption } } +func counselingOfficeFilter(office *string) QueryOption { + return func(query *pop.Query) { + if office != nil { + query.Where("transportation_offices.name ILIKE ?", "%"+*office+"%") + } + } +} + func moveStatusFilter(statuses []string) QueryOption { return func(query *pop.Query) { // If we have statuses let's use them @@ -699,6 +714,7 @@ func sortOrder(sort *string, order *string, ppmCloseoutGblocs bool) QueryOption "ppmStatus": "ppm_shipments.status", "closeoutLocation": "closeout_to.name", "closeoutInitiated": "MAX(ppm_shipments.submitted_at)", + "counselingOffice": "transportation_offices.name", "assignedTo": "assigned_user.last_name,assigned_user.first_name", } diff --git a/pkg/services/order/order_fetcher_test.go b/pkg/services/order/order_fetcher_test.go index 6f6b4f36a42..4b72919bb8f 100644 --- a/pkg/services/order/order_fetcher_test.go +++ b/pkg/services/order/order_fetcher_test.go @@ -580,7 +580,7 @@ func (suite *OrderServiceSuite) TestListOrders() { suite.Equal(createdPPM.Shipment.MoveTaskOrder.Locator, moves[0].Locator) }) } -func (suite *OrderServiceSuite) TestListOrderWithAssignedUser(){ +func (suite *OrderServiceSuite) TestListOrderWithAssignedUserSingle(){ // Under test: ListOrders // Set up: Make a move, assign one to an SC office user // Expected outcome: Only the one move with the assigned user should be returned @@ -599,6 +599,9 @@ func (suite *OrderServiceSuite) TestListOrderWithAssignedUser(){ createdMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + expectedMove := createdMove + expectedMove.SCAssignedID = scUser.UserID + expectedMove.SCAssignedUser = &scUser assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx,createdMove.ID,&scUser,roles.RoleTypeServicesCounselor) searchString := fmt.Sprintf("%s, %s",scUser.LastName,scUser.FirstName ) @@ -608,42 +611,8 @@ func (suite *OrderServiceSuite) TestListOrderWithAssignedUser(){ suite.FatalNoError(err) suite.Equal(1, len(moves)) - suite.Equal(moves[0].SCAssignedID, createdMove.SCAssignedID) - suite.Equal(moves[0].SCAssignedUser, createdMove.SCAssignedUser) -} - -func (suite *OrderServiceSuite) TestListOrderWithAssignedUser(){ - // Under test: ListOrders - // Set up: Make a move, assign one to an SC office user - // Expected outcome: Only the one move with the assigned user should be returned - assignedOfficeUserUpdater := moveservice.NewAssignedOfficeUserUpdater(moveservice.NewMoveFetcher()) - scUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - var orderFetcherTest orderFetcher - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: scUser.User.Roles, - OfficeUserID: scUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - appCtx := suite.AppContextWithSessionForTest(&session) - - - createdMoveWithAssigned := factory.BuildMoveWithShipment(suite.DB(), nil, nil) - factory.BuildMoveWithShipment(suite.DB(), nil, nil) - factory.BuildMoveWithShipment(suite.DB(), nil, nil) - assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx,createdMoveWithAssigned.ID,&scUser,roles.RoleTypeServicesCounselor) - - searchString := fmt.Sprintf("%s, %s",scUser.LastName,scUser.FirstName ) - moves, _, err := orderFetcherTest.ListOrders(suite.AppContextWithSessionForTest(&session), scUser.ID, &services.ListOrderParams{ - SCAssignedUser: &searchString, - }) - - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(moves[0].SCAssignedID, createdMoveWithAssigned.SCAssignedID) - suite.Equal(moves[0].SCAssignedUser, createdMoveWithAssigned.SCAssignedUser) + suite.Equal(moves[0].SCAssignedID, expectedMove.SCAssignedID) + suite.Equal(moves[0].SCAssignedUser, expectedMove.SCAssignedUser) } func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { orderFetcher := NewOrderFetcher() diff --git a/pkg/services/payment_request.go b/pkg/services/payment_request.go index 5f81f2215c9..25f62be43a4 100644 --- a/pkg/services/payment_request.go +++ b/pkg/services/payment_request.go @@ -5,6 +5,7 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/spf13/afero" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" @@ -115,3 +116,7 @@ type ShipmentPaymentSITBalance struct { type ShipmentsPaymentSITBalance interface { ListShipmentPaymentSITBalance(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) ([]ShipmentPaymentSITBalance, error) } + +type PaymentRequestBulkDownloadCreator interface { + CreatePaymentRequestBulkDownload(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) +} diff --git a/pkg/services/payment_request/payment_request_recalculator_test.go b/pkg/services/payment_request/payment_request_recalculator_test.go index 82da8ed74c2..55b4a2ec47c 100644 --- a/pkg/services/payment_request/payment_request_recalculator_test.go +++ b/pkg/services/payment_request/payment_request_recalculator_test.go @@ -23,8 +23,8 @@ import ( const ( recalculateTestPickupZip = "30907" recalculateTestDestinationZip = "78234" - recalculateTestMSFee = unit.Cents(25513) - recalculateTestCSFee = unit.Cents(22399) + recalculateTestMSFee = unit.Cents(12303) + recalculateTestCSFee = unit.Cents(12303) recalculateTestDLHPrice = unit.Millicents(6000) recalculateTestFSCPrice = unit.Millicents(277600) recalculateTestDomOtherPrice = unit.Cents(2159) @@ -154,12 +154,12 @@ func (suite *PaymentRequestServiceSuite) TestRecalculatePaymentRequestSuccess() { paymentRequest: &oldPaymentRequest, serviceCode: models.ReServiceCodeMS, - priceCents: unit.Cents(25513), + priceCents: unit.Cents(12303), }, { paymentRequest: &oldPaymentRequest, serviceCode: models.ReServiceCodeCS, - priceCents: unit.Cents(22399), + priceCents: unit.Cents(12303), }, { paymentRequest: &oldPaymentRequest, @@ -196,13 +196,13 @@ func (suite *PaymentRequestServiceSuite) TestRecalculatePaymentRequestSuccess() isNewPaymentRequest: true, paymentRequest: newPaymentRequest, serviceCode: models.ReServiceCodeMS, - priceCents: unit.Cents(25513), + priceCents: unit.Cents(12303), }, { isNewPaymentRequest: true, paymentRequest: newPaymentRequest, serviceCode: models.ReServiceCodeCS, - priceCents: unit.Cents(22399), + priceCents: unit.Cents(12303), }, { isNewPaymentRequest: true, @@ -407,24 +407,6 @@ func (suite *PaymentRequestServiceSuite) setupRecalculateData1() (models.Move, m }, }) - // MS price data - msService := factory.BuildReServiceByCode(suite.DB(), models.ReServiceCodeMS) - msTaskOrderFee := models.ReTaskOrderFee{ - ContractYearID: contractYear.ID, - ServiceID: msService.ID, - PriceCents: recalculateTestMSFee, - } - suite.MustSave(&msTaskOrderFee) - - // CS price data - csService := factory.BuildReServiceByCode(suite.DB(), models.ReServiceCodeCS) - csTaskOrderFee := models.ReTaskOrderFee{ - ContractYearID: contractYear.ID, - ServiceID: csService.ID, - PriceCents: recalculateTestCSFee, - } - suite.MustSave(&csTaskOrderFee) - // DLH price data testdatagen.MakeReDomesticLinehaulPrice(suite.DB(), testdatagen.Assertions{ ReDomesticLinehaulPrice: models.ReDomesticLinehaulPrice{ diff --git a/pkg/services/shipment_address_update/shipment_address_update_requester.go b/pkg/services/shipment_address_update/shipment_address_update_requester.go index 56460f7b1da..2e5a08a300f 100644 --- a/pkg/services/shipment_address_update/shipment_address_update_requester.go +++ b/pkg/services/shipment_address_update/shipment_address_update_requester.go @@ -245,7 +245,7 @@ func (f *shipmentAddressUpdateRequester) RequestShipmentDeliveryAddressUpdate(ap return nil, apperror.NewUnprocessableEntityError("destination address update requests can only be created for moves that are available to the Prime") } if shipment.ShipmentType != models.MTOShipmentTypeHHG && shipment.ShipmentType != models.MTOShipmentTypeHHGOutOfNTSDom { - return nil, apperror.NewUnprocessableEntityError("destination address update requests can only be created for HHG and NTSr shipments") + return nil, apperror.NewUnprocessableEntityError("destination address update requests can only be created for HHG and NTS-Release shipments") } if eTag != etag.GenerateEtag(shipment.UpdatedAt) { return nil, apperror.NewPreconditionFailedError(shipmentID, nil) diff --git a/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js b/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js index 5b6f56fbb57..a6cc84e9f04 100644 --- a/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js +++ b/playwright/tests/office/primesimulator/primeSimulatorFlows.spec.js @@ -148,7 +148,7 @@ test.describe('Prime simulator user', () => { await page.locator('input[name="destinationAddress.city"]').fill('Joshua Tree'); await page.locator('select[name="destinationAddress.state"]').selectOption({ label: 'CA' }); await page.locator('input[name="destinationAddress.postalCode"]').fill('92252'); - await page.getByTestId('dropdown').nth(1).selectOption('Home of record (HOR)'); + await page.getByTestId('dropdown').nth(5).selectOption('Home of record (HOR)'); await page.getByText('Save').click(); await expect(page.getByText('Successfully updated shipment')).toHaveCount(1); diff --git a/playwright/tests/utils/waitForPage.js b/playwright/tests/utils/waitForPage.js index 7a79f419f38..08f582ce3f9 100644 --- a/playwright/tests/utils/waitForPage.js +++ b/playwright/tests/utils/waitForPage.js @@ -19,18 +19,17 @@ export class WaitForPage { */ async runAccessibilityAudit() { - if (process.env.A11Y_AUDIT) { - await checkA11y( - this.page, - undefined, - { - detailedReport: true, - }, - // skip failures - true, - 'default', - ); - } + await checkA11y( + this.page, + undefined, + { + detailedReport: true, + detailedReportOptions: { html: true }, + }, + // skip failures + false, + 'html', + ); } /** diff --git a/scripts/run-e2e-test b/scripts/run-e2e-test index 92cce285789..e0272b245c0 100755 --- a/scripts/run-e2e-test +++ b/scripts/run-e2e-test @@ -31,4 +31,4 @@ done trap cleanup SIGINT trap cleanup exit -yarn playwright test "$@" +A11Y_AUDIT=true yarn playwright test "$@" diff --git a/src/components/CustomerHeader/CustomerHeader.test.jsx b/src/components/CustomerHeader/CustomerHeader.test.jsx index 1668afb9f29..fec1c7c7b73 100644 --- a/src/components/CustomerHeader/CustomerHeader.test.jsx +++ b/src/components/CustomerHeader/CustomerHeader.test.jsx @@ -5,7 +5,7 @@ import { mount } from 'enzyme'; import CustomerHeader from './index'; const props = { - customer: { last_name: 'Kerry', first_name: 'Smith', dodID: '999999999', emplid: '7777777', agency: 'COAST_GUARD' }, + customer: { last_name: 'Kerry', first_name: 'Smith', edipi: '999999999', emplid: '7777777', agency: 'COAST_GUARD' }, order: { agency: 'COAST_GUARD', grade: 'E_6', @@ -25,7 +25,7 @@ const props = { }; const propsRetiree = { - customer: { last_name: 'Kerry', first_name: 'Smith', dodID: '999999999' }, + customer: { last_name: 'Kerry', first_name: 'Smith', edipi: '999999999' }, order: { agency: 'NAVY', grade: 'E_6', @@ -45,7 +45,7 @@ const propsRetiree = { }; const propsUSMC = { - customer: { last_name: 'Kerry', first_name: 'Smith', dodID: '999999999' }, + customer: { last_name: 'Kerry', first_name: 'Smith', edipi: '999999999' }, order: { agency: 'MARINES', grade: 'E_6', @@ -77,7 +77,7 @@ describe('CustomerHeader component', () => { expect(wrapper.find('[data-testid="nameBlock"]').text()).toContain('Kerry, Smith'); expect(wrapper.find('[data-testid="nameBlock"]').text()).toContain('FKLCTR'); expect(wrapper.find('[data-testid="deptPayGrade"]').text()).toContain('Coast Guard E-6'); - expect(wrapper.find('[data-testid="dodId"]').text()).toContain('DoD ID 999999999'); + expect(wrapper.find('[data-testid="edipi"]').text()).toContain('DoD ID 999999999'); expect(wrapper.find('[data-testid="emplid"]').text()).toContain('EMPLID 7777777'); expect(wrapper.find('[data-testid="infoBlock"]').text()).toContain('JBSA Lackland'); expect(wrapper.find('[data-testid="infoBlock"]').text()).toContain('JB Lewis-McChord'); diff --git a/src/components/CustomerHeader/index.jsx b/src/components/CustomerHeader/index.jsx index 36d8ebc037a..857e2469997 100644 --- a/src/components/CustomerHeader/index.jsx +++ b/src/components/CustomerHeader/index.jsx @@ -55,8 +55,8 @@ const CustomerHeader = ({ customer, order, moveCode, move, userRole }) => { {ORDERS_BRANCH_OPTIONS[`${order.agency}`]} {ORDERS_PAY_GRADE_OPTIONS[`${order.grade}`]} | - - DoD ID {customer.dodID} + + DoD ID {customer.edipi} {isCoastGuard && ( <> diff --git a/src/components/DocumentViewer/DocumentViewer.jsx b/src/components/DocumentViewer/DocumentViewer.jsx index 98494e3f163..73daff482d3 100644 --- a/src/components/DocumentViewer/DocumentViewer.jsx +++ b/src/components/DocumentViewer/DocumentViewer.jsx @@ -12,9 +12,10 @@ import Menu from './Menu/Menu'; import { milmoveLogger } from 'utils/milmoveLog'; import { UPLOADS } from 'constants/queryKeys'; -import { updateUpload } from 'services/ghcApi'; +import { bulkDownloadPaymentRequest, updateUpload } from 'services/ghcApi'; import { formatDate } from 'shared/dates'; import { filenameFromPath } from 'utils/formatters'; +import AsyncPacketDownloadLink from 'shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink'; /** * TODO @@ -23,7 +24,7 @@ import { filenameFromPath } from 'utils/formatters'; * - handle fetch doc errors */ -const DocumentViewer = ({ files, allowDownload }) => { +const DocumentViewer = ({ files, allowDownload, paymentRequestId }) => { const [selectedFileIndex, selectFile] = useState(0); const [disableSaveButton, setDisableSaveButton] = useState(false); const [menuIsOpen, setMenuOpen] = useState(false); @@ -117,6 +118,20 @@ const DocumentViewer = ({ files, allowDownload }) => { } }; + const paymentPacketDownload = ( +
+
+

+ +

+
+
+ ); + return (
@@ -133,6 +148,7 @@ const DocumentViewer = ({ files, allowDownload }) => {

)} + {paymentRequestId !== undefined ? paymentPacketDownload : null}
{ const container = document.querySelector('[data-testid="menuButtonContainer"]'); if (container) { @@ -50,6 +52,11 @@ const mockFiles = [ }, ]; +jest.mock('services/ghcApi', () => ({ + ...jest.requireActual('services/ghcApi'), + bulkDownloadPaymentRequest: jest.fn(), +})); + jest.mock('./Content/Content', () => ({ __esModule: true, default: ({ id, filename, contentType, url, createdAt, rotation }) => ( @@ -172,4 +179,37 @@ describe('DocumentViewer component', () => { expect(screen.getByText('id: undefined')).toBeInTheDocument(); }); + + describe('when clicking download Download All Files button', () => { + it('downloads a bulk packet', async () => { + const mockResponse = { + ok: true, + headers: { + 'content-disposition': 'filename="test.pdf"', + }, + status: 200, + data: null, + }; + + render( + + + , + ); + + bulkDownloadPaymentRequest.mockImplementation(() => Promise.resolve(mockResponse)); + + const downloadButton = screen.getByText('Download All Files (PDF)', { exact: false }); + await userEvent.click(downloadButton); + await waitFor(() => { + expect(bulkDownloadPaymentRequest).toHaveBeenCalledTimes(1); + }); + }); + }); }); 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/PaymentRequestCard/PaymentRequestCard.jsx b/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx index 8e1ed250eec..88ab4ab916d 100644 --- a/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx +++ b/src/components/Office/PaymentRequestCard/PaymentRequestCard.jsx @@ -265,12 +265,8 @@ const PaymentRequestCard = ({ ); }; - const renderPaymentRequestDetailsForStatus = (paymentRequestStatus) => { - if ( - (paymentRequestStatus === PAYMENT_REQUEST_STATUS.PAID || - paymentRequestStatus === PAYMENT_REQUEST_STATUS.EDI_ERROR) && - tppsInvoiceSellerPaidDate - ) { + const renderApprovedRejectedPaymentRequestDetails = () => { + if (approvedAmount > 0 || rejectedAmount > 0) { return (
{approvedAmount > 0 && ( @@ -293,6 +289,20 @@ const PaymentRequestCard = ({
)} +
+ ); + } + return null; + }; + + const renderPaymentRequestDetailsForStatus = (paymentRequestStatus) => { + if ( + (paymentRequestStatus === PAYMENT_REQUEST_STATUS.PAID || + paymentRequestStatus === PAYMENT_REQUEST_STATUS.EDI_ERROR) && + tppsInvoiceSellerPaidDate + ) { + return ( +
{tppsInvoiceAmountPaidTotalMillicents > 0 && (
@@ -313,22 +323,12 @@ const PaymentRequestCard = ({ ) { return (
- {approvedAmount > 0 && ( + {paymentRequest.receivedByGexAt && (
-
+

{toDollarString(formatCents(approvedAmount))}

- Received - on {formatDateFromIso(paymentRequest.receivedByGexAt, 'DD MMM YYYY')} -
-
- )} - {rejectedAmount > 0 && ( -
- -
-

{toDollarString(formatCents(rejectedAmount))}

- Rejected + TPPS Received on {formatDateFromIso(paymentRequest.receivedByGexAt, 'DD MMM YYYY')}
@@ -336,36 +336,7 @@ const PaymentRequestCard = ({
); } - if ( - paymentRequestStatus === PAYMENT_REQUEST_STATUS.REVIEWED || - paymentRequestStatus === PAYMENT_REQUEST_STATUS.REVIEWED_AND_ALL_SERVICE_ITEMS_REJECTED || - paymentRequestStatus === PAYMENT_REQUEST_STATUS.EDI_ERROR - ) { - return ( -
- {approvedAmount > 0 && ( -
- -
-

{toDollarString(formatCents(approvedAmount))}

- Accepted - on {formatDateFromIso(paymentRequest.reviewedAt, 'DD MMM YYYY')} -
-
- )} - {rejectedAmount > 0 && ( -
- -
-

{toDollarString(formatCents(rejectedAmount))}

- Rejected - on {formatDateFromIso(paymentRequest.reviewedAt, 'DD MMM YYYY')} -
-
- )} -
- ); - } + if ( paymentRequestStatus === PAYMENT_REQUEST_STATUS.SENT_TO_GEX || (paymentRequestStatus === PAYMENT_REQUEST_STATUS.EDI_ERROR && approvedAmount > 0) @@ -373,7 +344,7 @@ const PaymentRequestCard = ({ return (
-
+

{toDollarString(formatCents(approvedAmount))}

Sent to GEX @@ -395,7 +366,7 @@ const PaymentRequestCard = ({
); } - return
; + return null; }; return ( @@ -420,7 +391,10 @@ const PaymentRequestCard = ({
-
{paymentRequest.status && renderPaymentRequestDetailsForStatus(paymentRequest.status)}
+
+ {paymentRequest.status && renderApprovedRejectedPaymentRequestDetails(paymentRequest)} + {paymentRequest.status && renderPaymentRequestDetailsForStatus(paymentRequest.status)} +
{paymentRequest.status === PAYMENT_REQUEST_STATUS.PENDING && renderReviewServiceItemsBtnForTIOandTOO()}
{ediErrorsExistForPaymentRequest && renderEDIErrorDetails()} diff --git a/src/components/Office/PaymentRequestCard/PaymentRequestCard.test.jsx b/src/components/Office/PaymentRequestCard/PaymentRequestCard.test.jsx index 04486d323c7..1443e72d928 100644 --- a/src/components/Office/PaymentRequestCard/PaymentRequestCard.test.jsx +++ b/src/components/Office/PaymentRequestCard/PaymentRequestCard.test.jsx @@ -601,7 +601,7 @@ describe('PaymentRequestCard', () => { createdAt: '2020-12-01T00:00:00.000Z', mtoServiceItemID: 'f8c2f97f-99e7-4fb1-9cc4-473debd24dbc', priceCents: 2000001, - status: 'DENIED', + status: 'APPROVED', }, { id: '39474c6a-69b6-4501-8e08-670a12512a5f', @@ -626,6 +626,14 @@ describe('PaymentRequestCard', () => { ); expect(sentToGex.find({ 'data-testid': 'tag' }).contains('Sent to GEX')).toBe(true); expect(sentToGex.find({ 'data-testid': 'sentToGexDetails' }).exists()).toBe(true); + // displays the sent to gex sum, milmove accepted amount, and milmove rejected amount + expect(sentToGex.find({ 'data-testid': 'sentToGexDetailsDollarAmountTotal' }).contains('$20,000.01')).toBe(true); + expect(sentToGex.find({ 'data-testid': 'milMoveAcceptedDetailsDollarAmountTotal' }).contains('$20,000.01')).toBe( + true, + ); + expect(sentToGex.find({ 'data-testid': 'milMoveRejectedDetailsDollarAmountTotal' }).contains('$40,000.01')).toBe( + true, + ); }); it('renders - for the date it was sent to gex if sentToGexAt is null', () => { @@ -653,13 +661,14 @@ describe('PaymentRequestCard', () => { paymentRequestNumber: '1843-9061-2', status: 'TPPS_RECEIVED', moveTaskOrder: move, + receivedByGexAt: '2020-12-01T00:00:00.000Z', serviceItems: [ { id: '09474c6a-69b6-4501-8e08-670a12512a5f', createdAt: '2020-12-01T00:00:00.000Z', mtoServiceItemID: 'f8c2f97f-99e7-4fb1-9cc4-473debd24dbc', priceCents: 2000001, - status: 'DENIED', + status: 'APPROVED', }, { id: '39474c6a-69b6-4501-8e08-670a12512a5f', @@ -681,6 +690,16 @@ describe('PaymentRequestCard', () => { , ); expect(receivedByGex.find({ 'data-testid': 'tag' }).contains('TPPS Received')).toBe(true); + // displays the tpps received sum, milmove accepted amount, and milmove rejected amount + expect(receivedByGex.find({ 'data-testid': 'tppsReceivedDetailsDollarAmountTotal' }).contains('$20,000.01')).toBe( + true, + ); + expect( + receivedByGex.find({ 'data-testid': 'milMoveAcceptedDetailsDollarAmountTotal' }).contains('$20,000.01'), + ).toBe(true); + expect( + receivedByGex.find({ 'data-testid': 'milMoveRejectedDetailsDollarAmountTotal' }).contains('$40,000.01'), + ).toBe(true); }); it('renders the paid status tag for paid request', () => { 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..f7849d7585a 100644 --- a/src/components/PrimeUI/Shipment/Shipment.jsx +++ b/src/components/PrimeUI/Shipment/Shipment.jsx @@ -13,7 +13,7 @@ import { ShipmentShape } from 'types/shipment'; import { primeSimulatorRoutes } from 'constants/routes'; import { ppmShipmentStatuses, shipmentDestinationTypes } from 'constants/shipments'; import styles from 'pages/PrimeUI/MoveTaskOrder/MoveDetails.module.scss'; -import { SHIPMENT_OPTIONS } from 'shared/constants'; +import { ADDRESS_TYPES, SHIPMENT_OPTIONS } from 'shared/constants'; const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => { const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false); @@ -188,12 +188,68 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => {
Pickup Address:
{formatPrimeAPIShipmentAddress(shipment.pickupAddress)}
-
{shipment.pickupAddress?.id && moveId && Edit}
+
+ {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}
+
+ {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 +327,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/SearchResultsTable.jsx b/src/components/Table/SearchResultsTable.jsx index 78a0daf5180..f36fc385faa 100644 --- a/src/components/Table/SearchResultsTable.jsx +++ b/src/components/Table/SearchResultsTable.jsx @@ -13,12 +13,7 @@ import DateSelectFilter from 'components/Table/Filters/DateSelectFilter'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; import TextBoxFilter from 'components/Table/Filters/TextBoxFilter'; -import { - BRANCH_OPTIONS_WITH_MARINE_CORPS, - MOVE_STATUS_LABELS, - SEARCH_QUEUE_STATUS_FILTER_OPTIONS, - SortShape, -} from 'constants/queues'; +import { BRANCH_OPTIONS, MOVE_STATUS_LABELS, SEARCH_QUEUE_STATUS_FILTER_OPTIONS, SortShape } from 'constants/queues'; import { DATE_FORMAT_STRING } from 'shared/constants'; import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; import MultiSelectCheckBoxFilter from 'components/Table/Filters/MultiSelectCheckBoxFilter'; @@ -107,7 +102,7 @@ const moveSearchColumns = (moveLockFlag, handleEditProfileClick) => [ isFilterable: true, Filter: (props) => ( // eslint-disable-next-line react/jsx-props-no-spreading - + ), }, ), 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 086fc79e5e2..b00e60b0140 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -49,8 +49,7 @@ const TableQueue = ({ csvExportQueueFetcher, csvExportQueueFetcherKey, sessionStorageKey, - isSupervisor, - currentUserId, + isHeadquartersUser, }) => { const [isPageReload, setIsPageReload] = useState(true); useEffect(() => { @@ -90,7 +89,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 = ','; @@ -324,6 +323,7 @@ const TableQueue = ({ totalCount={totalCount} paramSort={paramSort} paramFilters={paramFilters} + isHeadquartersUser={isHeadquartersUser} /> )}

@@ -393,6 +393,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 = { @@ -411,5 +413,6 @@ TableQueue.defaultProps = { csvExportQueueFetcher: null, csvExportQueueFetcherKey: null, sessionStorageKey: 'default', + isHeadquartersUser: false, }; export default TableQueue; diff --git a/src/constants/queues.js b/src/constants/queues.js index 87d23e1ed04..17a6e9d4066 100644 --- a/src/constants/queues.js +++ b/src/constants/queues.js @@ -52,15 +52,6 @@ export const BRANCH_OPTIONS = [ { value: 'AIR_FORCE', label: 'Air Force' }, { value: 'COAST_GUARD', label: 'Coast Guard' }, { value: 'SPACE_FORCE', label: 'Space Force' }, -]; - -export const BRANCH_OPTIONS_WITH_MARINE_CORPS = [ - { value: '', label: 'All' }, - { value: 'ARMY', label: 'Army' }, - { value: 'NAVY', label: 'Navy' }, - { value: 'AIR_FORCE', label: 'Air Force' }, - { value: 'COAST_GUARD', label: 'Coast Guard' }, - { value: 'SPACE_FORCE', label: 'Space Force' }, { value: 'MARINES', label: 'Marine Corps' }, ]; 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..69354c506e6 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,26 @@ 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'), + // All branches require an EDIPI unless it is a safety move + // where a fake DoD ID may be used + edipi: + !isSafetyMove && + Yup.string() + .matches(/^(SM[0-9]{8}|[0-9]{10})$/, 'Enter a 10-digit DoD ID number') + .required('Required'), + // Only the coast guard requires both EDIPI and EMPLID + // unless it is a safety move + emplid: + !isSafetyMove && + showEmplid && + Yup.string().when('affiliation', { + is: (affiliationValue) => affiliationValue === departmentIndicators.COAST_GUARD, + then: () => + Yup.string() + .matches(/^(SM[0-9]{5}|[0-9]{7})$/, 'Enter a 7-digit EMPLID number') + .required(`EMPLID is required for the Coast Guard`), + otherwise: Yup.string().notRequired(), + }), first_name: Yup.string().required('Required'), middle_name: Yup.string(), last_name: Yup.string().required('Required'), @@ -193,11 +216,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 +228,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 +288,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 +318,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..841f7eb7c5e 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,67 @@ 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 userEvent.type(getByTestId('edipiInput'), fakePayload.edipi); + + 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( @@ -249,6 +310,8 @@ describe('CreateCustomerForm', () => { await user.type(getByLabelText('Best contact phone'), fakePayload.telephone); await user.type(getByLabelText('Personal email'), fakePayload.personal_email); + await userEvent.type(getByTestId('edipiInput'), fakePayload.edipi); + await userEvent.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1); await userEvent.type(getByTestId('res-add-city'), fakePayload.residential_address.city); await userEvent.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]); @@ -282,6 +345,57 @@ describe('CreateCustomerForm', () => { }); }, 10000); + it('validates emplid against a coast guard member', 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'), 'COAST_GUARD'); + + 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 userEvent.type(getByTestId('edipiInput'), fakePayload.edipi); + + await userEvent.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1); + await userEvent.type(getByTestId('res-add-city'), fakePayload.residential_address.city); + await userEvent.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]); + await userEvent.type(getByTestId('res-add-zip'), fakePayload.residential_address.postalCode); + + await userEvent.type(getByTestId('backup-add-street1'), fakePayload.backup_mailing_address.streetAddress1); + await userEvent.type(getByTestId('backup-add-city'), fakePayload.backup_mailing_address.city); + await userEvent.selectOptions(getByTestId('backup-add-state'), [fakePayload.backup_mailing_address.state]); + await userEvent.type(getByTestId('backup-add-zip'), fakePayload.backup_mailing_address.postalCode); + + await userEvent.type(getByLabelText('Name'), fakePayload.backup_contact.name); + await userEvent.type(getByRole('textbox', { name: 'Email' }), fakePayload.backup_contact.email); + await userEvent.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).toBeDisabled(); // EMPLID not set yet + }); + await userEvent.type(getByTestId('emplidInput'), '1234567'); + await waitFor(() => { + expect(saveBtn).toBeEnabled(); // EMPLID is set now, all validations true + }); + }, 10000); + it('allows safety privileged users to pass safety move status to orders screen', async () => { createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); @@ -335,6 +449,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)); @@ -350,6 +529,7 @@ describe('CreateCustomerForm', () => { expect(saveBtn).toBeInTheDocument(); await user.selectOptions(getByLabelText('Branch of service'), [fakePayload.affiliation]); + await userEvent.type(getByTestId('edipiInput'), fakePayload.edipi); await user.type(getByLabelText('First name'), fakePayload.first_name); await user.type(getByLabelText('Last name'), fakePayload.last_name); 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/MoveQueue/MoveQueue.jsx b/src/pages/Office/MoveQueue/MoveQueue.jsx index d485648b3fe..acfceb62439 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.jsx @@ -10,7 +10,7 @@ import { getMovesQueue } from 'services/ghcApi'; import { formatDateFromIso, serviceMemberAgencyLabel } from 'utils/formatters'; import MultiSelectCheckBoxFilter from 'components/Table/Filters/MultiSelectCheckBoxFilter'; import SelectFilter from 'components/Table/Filters/SelectFilter'; -import { BRANCH_OPTIONS, MOVE_STATUS_OPTIONS, GBLOC, MOVE_STATUS_LABELS } from 'constants/queues'; +import { MOVE_STATUS_OPTIONS, GBLOC, MOVE_STATUS_LABELS, BRANCH_OPTIONS } from 'constants/queues'; import TableQueue from 'components/Table/TableQueue'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; diff --git a/src/pages/Office/MoveQueue/MoveQueue.test.jsx b/src/pages/Office/MoveQueue/MoveQueue.test.jsx index f62a6b75c05..b8017b59fdb 100644 --- a/src/pages/Office/MoveQueue/MoveQueue.test.jsx +++ b/src/pages/Office/MoveQueue/MoveQueue.test.jsx @@ -7,7 +7,7 @@ import { render, screen, waitFor } from '@testing-library/react'; import MoveQueue from './MoveQueue'; import { MockProviders } from 'testUtils'; -import { MOVE_STATUS_OPTIONS } from 'constants/queues'; +import { MOVE_STATUS_OPTIONS, BRANCH_OPTIONS } from 'constants/queues'; import { generalRoutes, tooRoutes } from 'constants/routes'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; @@ -23,6 +23,71 @@ jest.mock('utils/featureFlags', () => ({ isBooleanFlagEnabled: jest.fn().mockImplementation(() => Promise.resolve()), })); +const moveData = [ + { + id: 'move1', + customer: { + agency: 'AIR_FORCE', + first_name: 'test first', + last_name: 'test last', + dodID: '555555555', + }, + locator: 'AB5P', + departmentIndicator: 'ARMY', + shipmentsCount: 2, + status: 'SUBMITTED', + originDutyLocation: { + name: 'Area 51', + }, + originGBLOC: 'EEEE', + requestedMoveDate: '2023-02-10', + appearedInTooAt: '2023-02-10T00:00:00.000Z', + lockExpiresAt: '2099-02-10T00:00:00.000Z', + lockedByOfficeUserID: '2744435d-7ba8-4cc5-bae5-f302c72c966e', + }, + { + id: 'move2', + customer: { + agency: 'COAST_GUARD', + first_name: 'test another first', + last_name: 'test another last', + dodID: '4444444444', + emplid: '4589652', + }, + locator: 'T12A', + departmentIndicator: 'COAST_GUARD', + shipmentsCount: 1, + status: 'APPROVED', + originDutyLocation: { + name: 'Los Alamos', + }, + originGBLOC: 'EEEE', + requestedMoveDate: '2023-02-12', + appearedInTooAt: '2023-02-12T00:00:00.000Z', + }, + { + id: 'move3', + customer: { + agency: 'Marine Corps', + first_name: 'will', + last_name: 'robinson', + dodID: '6666666666', + }, + locator: 'PREP', + departmentIndicator: 'MARINES', + shipmentsCount: 1, + status: 'SUBMITTED', + originDutyLocation: { + name: 'Area 52', + }, + originGBLOC: 'EEEE', + requestedMoveDate: '2023-03-12', + appearedInTooAt: '2023-03-12T00:00:00.000Z', + lockExpiresAt: '2099-03-12T00:00:00.000Z', + lockedByOfficeUserID: '2744435d-7ba8-4cc5-bae5-f302c72c966e', + }, +]; + jest.mock('hooks/queries', () => ({ useUserQueries: () => { return { @@ -38,50 +103,8 @@ jest.mock('hooks/queries', () => ({ isLoading: false, isError: false, queueResult: { - totalCount: 2, - data: [ - { - id: 'move1', - customer: { - agency: 'AIR_FORCE', - first_name: 'test first', - last_name: 'test last', - dodID: '555555555', - }, - locator: 'AB5P', - departmentIndicator: 'ARMY', - shipmentsCount: 2, - status: 'SUBMITTED', - originDutyLocation: { - name: 'Area 51', - }, - originGBLOC: 'EEEE', - requestedMoveDate: '2023-02-10', - appearedInTooAt: '2023-02-10T00:00:00.000Z', - lockExpiresAt: '2099-02-10T00:00:00.000Z', - lockedByOfficeUserID: '2744435d-7ba8-4cc5-bae5-f302c72c966e', - }, - { - id: 'move2', - customer: { - agency: 'COAST_GUARD', - first_name: 'test another first', - last_name: 'test another last', - dodID: '4444444444', - emplid: '4589652', - }, - locator: 'T12A', - departmentIndicator: 'COAST_GUARD', - shipmentsCount: 1, - status: 'APPROVED', - originDutyLocation: { - name: 'Los Alamos', - }, - originGBLOC: 'EEEE', - requestedMoveDate: '2023-02-12', - appearedInTooAt: '2023-02-12T00:00:00.000Z', - }, - ], + totalCount: 3, + data: moveData, }, }; }, @@ -103,7 +126,7 @@ describe('MoveQueue', () => { }); it('should render the h1', () => { - expect(GetMountedComponent(tooRoutes.MOVE_QUEUE).find('h1').text()).toBe('All moves (2)'); + expect(GetMountedComponent(tooRoutes.MOVE_QUEUE).find('h1').text()).toBe('All moves (3)'); }); it('should render the table', () => { @@ -111,32 +134,89 @@ describe('MoveQueue', () => { }); it('should format the column data', () => { + let currentIndex = 0; + let currentMove; const moves = GetMountedComponent(tooRoutes.MOVE_QUEUE).find('tbody tr'); - const firstMove = moves.at(0); - expect(firstMove.find({ 'data-testid': 'lastName-0' }).text()).toBe('test last, test first'); - expect(firstMove.find({ 'data-testid': 'dodID-0' }).text()).toBe('555555555'); - expect(firstMove.find({ 'data-testid': 'status-0' }).text()).toBe('New move'); - expect(firstMove.find({ 'data-testid': 'locator-0' }).text()).toBe('AB5P'); - expect(firstMove.find({ 'data-testid': 'branch-0' }).text()).toBe('Air Force'); - expect(firstMove.find({ 'data-testid': 'shipmentsCount-0' }).text()).toBe('2'); - expect(firstMove.find({ 'data-testid': 'originDutyLocation-0' }).text()).toBe('Area 51'); - expect(firstMove.find({ 'data-testid': 'originGBLOC-0' }).text()).toBe('EEEE'); - expect(firstMove.find({ 'data-testid': 'requestedMoveDate-0' }).text()).toBe('10 Feb 2023'); - expect(firstMove.find({ 'data-testid': 'appearedInTooAt-0' }).text()).toBe('10 Feb 2023'); + currentMove = moves.at(currentIndex); + expect(currentMove.find({ 'data-testid': `lastName-${currentIndex}` }).text()).toBe( + `${moveData[currentIndex].customer.last_name}, ${moveData[currentIndex].customer.first_name}`, + ); + expect(currentMove.find({ 'data-testid': `dodID-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.dodID, + ); + expect(currentMove.find({ 'data-testid': `status-${currentIndex}` }).text()).toBe('New move'); + expect(currentMove.find({ 'data-testid': `locator-${currentIndex}` }).text()).toBe(moveData[currentIndex].locator); + expect(currentMove.find({ 'data-testid': `branch-${currentIndex}` }).text()).toBe( + BRANCH_OPTIONS.find((value) => value.value === moveData[currentIndex].customer.agency).label, + ); + expect(currentMove.find({ 'data-testid': `shipmentsCount-${currentIndex}` }).text()).toBe( + moveData[currentIndex].shipmentsCount.toString(), + ); + expect(currentMove.find({ 'data-testid': `originDutyLocation-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originDutyLocation.name, + ); + expect(currentMove.find({ 'data-testid': `originGBLOC-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originGBLOC, + ); + expect(currentMove.find({ 'data-testid': `requestedMoveDate-${currentIndex}` }).text()).toBe('10 Feb 2023'); + expect(currentMove.find({ 'data-testid': `appearedInTooAt-${currentIndex}` }).text()).toBe('10 Feb 2023'); + + currentIndex += 1; + currentMove = moves.at(currentIndex); + expect(currentMove.find({ 'data-testid': `lastName-${currentIndex}` }).text()).toBe( + 'test another last, test another first', + ); + expect(currentMove.find({ 'data-testid': `lastName-${currentIndex}` }).text()).toBe( + `${moveData[currentIndex].customer.last_name}, ${moveData[currentIndex].customer.first_name}`, + ); + expect(currentMove.find({ 'data-testid': `dodID-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.dodID, + ); + expect(currentMove.find({ 'data-testid': `emplid-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.emplid, + ); + expect(currentMove.find({ 'data-testid': `status-${currentIndex}` }).text()).toBe('Move approved'); + expect(currentMove.find({ 'data-testid': `locator-${currentIndex}` }).text()).toBe(moveData[currentIndex].locator); + expect(currentMove.find({ 'data-testid': `branch-${currentIndex}` }).text()).toBe( + BRANCH_OPTIONS.find((value) => value.value === moveData[currentIndex].customer.agency).label, + ); + expect(currentMove.find({ 'data-testid': `shipmentsCount-${currentIndex}` }).text()).toBe( + moveData[currentIndex].shipmentsCount.toString(), + ); + expect(currentMove.find({ 'data-testid': `originDutyLocation-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originDutyLocation.name, + ); + expect(currentMove.find({ 'data-testid': `originGBLOC-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originGBLOC, + ); + expect(currentMove.find({ 'data-testid': `requestedMoveDate-${currentIndex}` }).text()).toBe('12 Feb 2023'); + expect(currentMove.find({ 'data-testid': `appearedInTooAt-${currentIndex}` }).text()).toBe('12 Feb 2023'); - const secondMove = moves.at(1); - expect(secondMove.find({ 'data-testid': 'lastName-1' }).text()).toBe('test another last, test another first'); - expect(secondMove.find({ 'data-testid': 'dodID-1' }).text()).toBe('4444444444'); - expect(secondMove.find({ 'data-testid': 'emplid-1' }).text()).toBe('4589652'); - expect(secondMove.find({ 'data-testid': 'status-1' }).text()).toBe('Move approved'); - expect(secondMove.find({ 'data-testid': 'locator-1' }).text()).toBe('T12A'); - expect(secondMove.find({ 'data-testid': 'branch-1' }).text()).toBe('Coast Guard'); - expect(secondMove.find({ 'data-testid': 'shipmentsCount-1' }).text()).toBe('1'); - expect(secondMove.find({ 'data-testid': 'originDutyLocation-1' }).text()).toBe('Los Alamos'); - expect(secondMove.find({ 'data-testid': 'originGBLOC-1' }).text()).toBe('EEEE'); - expect(secondMove.find({ 'data-testid': 'requestedMoveDate-1' }).text()).toBe('12 Feb 2023'); - expect(secondMove.find({ 'data-testid': 'appearedInTooAt-1' }).text()).toBe('12 Feb 2023'); + currentIndex += 1; + currentMove = moves.at(currentIndex); + expect(currentMove.find({ 'data-testid': `lastName-${currentIndex}` }).text()).toBe( + `${moveData[currentIndex].customer.last_name}, ${moveData[currentIndex].customer.first_name}`, + ); + expect(currentMove.find({ 'data-testid': `dodID-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.dodID, + ); + expect(currentMove.find({ 'data-testid': `status-${currentIndex}` }).text()).toBe('New move'); + expect(currentMove.find({ 'data-testid': `locator-${currentIndex}` }).text()).toBe(moveData[currentIndex].locator); + expect(currentMove.find({ 'data-testid': `branch-${currentIndex}` }).text()).toBe( + moveData[currentIndex].customer.agency.toString(), + ); + expect(currentMove.find({ 'data-testid': `shipmentsCount-${currentIndex}` }).text()).toBe( + moveData[currentIndex].shipmentsCount.toString(), + ); + expect(currentMove.find({ 'data-testid': `originDutyLocation-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originDutyLocation.name, + ); + expect(currentMove.find({ 'data-testid': `originGBLOC-${currentIndex}` }).text()).toBe( + moveData[currentIndex].originGBLOC, + ); + expect(currentMove.find({ 'data-testid': `requestedMoveDate-${currentIndex}` }).text()).toBe('12 Mar 2023'); + expect(currentMove.find({ 'data-testid': `appearedInTooAt-${currentIndex}` }).text()).toBe('12 Mar 2023'); }); it('should render the pagination component', () => { @@ -249,7 +329,7 @@ describe('MoveQueue', () => { , ); await waitFor(() => { - const lockIcon = screen.queryByTestId('lock-icon'); + const lockIcon = screen.queryAllByTestId('lock-icon')[0]; expect(lockIcon).toBeInTheDocument(); }); }); diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx index e4a3f2c576f..e20af2c740d 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx @@ -175,7 +175,11 @@ export const PaymentRequestReview = ({ order }) => { return (
- {uploads.length > 0 ? :

No documents provided

} + {uploads.length > 0 ? ( + + ) : ( +

No documents provided

+ )}
{ expect(reviewServiceItems.prop('serviceItemCards')).toEqual(expectedServiceItemCards); }); }); + describe('clicking the next button', () => { describe('with pending requests', () => { beforeEach(async () => { diff --git a/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx b/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx index de1ac1b0ead..26b06a51d85 100644 --- a/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx +++ b/src/pages/Office/ServicesCounselingEditShipmentDetails/ServicesCounselingEditShipmentDetails.test.jsx @@ -328,7 +328,7 @@ describe('ServicesCounselingEditShipmentDetails component', () => { expect( screen.getByText('Something went wrong, and your changes were not saved. Please try again.'), ).toBeVisible(); - }); + }, 10000); }); it('routes to the move details page when the cancel button is clicked', async () => { 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 4cac028ca59..68940e9cb55 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -10,7 +10,7 @@ import SelectFilter from 'components/Table/Filters/SelectFilter'; import DateSelectFilter from 'components/Table/Filters/DateSelectFilter'; import TableQueue from 'components/Table/TableQueue'; import { - BRANCH_OPTIONS_WITH_MARINE_CORPS, + BRANCH_OPTIONS, SERVICE_COUNSELING_MOVE_STATUS_LABELS, SERVICE_COUNSELING_PPM_TYPE_OPTIONS, SERVICE_COUNSELING_PPM_TYPE_LABELS, @@ -50,167 +50,168 @@ import CustomerSearchForm from 'components/CustomerSearchForm/CustomerSearchForm import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSelectTypeAheadCheckboxFilter'; import { formatAvailableOfficeUsersForRow, handleQueueAssignment } from 'utils/queues'; -export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => { - const cols = [ - createHeader( - ' ', - (row) => { - const now = new Date(); - // this will render a lock icon if the move is locked & if the lockExpiresAt value is after right now - if (row.lockedByOfficeUserID && row.lockExpiresAt && now < new Date(row.lockExpiresAt) && moveLockFlag) { - return ( -
- -
- ); - } - return null; - }, - { id: 'lock' }, - ), - createHeader('ID', 'id', { id: 'id' }), - createHeader( - 'Customer name', - (row) => { +export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => [ + createHeader( + ' ', + (row) => { + const now = new Date(); + // this will render a lock icon if the move is locked & if the lockExpiresAt value is after right now + if (row.lockedByOfficeUserID && row.lockExpiresAt && now < new Date(row.lockExpiresAt) && moveLockFlag) { return ( -
- {CHECK_SPECIAL_ORDERS_TYPES(row.orderType) ? ( - {SPECIAL_ORDERS_TYPES[`${row.orderType}`]} - ) : null} - {`${row.customer.last_name}, ${row.customer.first_name}`} +
+
); - }, - { - id: 'lastName', - isFilterable: true, - exportValue: (row) => { - return `${row.customer.last_name}, ${row.customer.first_name}`; - }, - }, - ), - createHeader('DoD ID', 'customer.dodID', { - id: 'dodID', + } + return null; + }, + { id: 'lock' }, + ), + createHeader('ID', 'id', { id: 'id' }), + createHeader( + 'Customer name', + (row) => { + return ( +
+ {CHECK_SPECIAL_ORDERS_TYPES(row.orderType) ? ( + {SPECIAL_ORDERS_TYPES[`${row.orderType}`]} + ) : null} + {`${row.customer.last_name}, ${row.customer.first_name}`} +
+ ); + }, + { + id: 'lastName', isFilterable: true, exportValue: (row) => { - return row.customer.dodID; + return `${row.customer.last_name}, ${row.customer.first_name}`; }, - }), - createHeader('EMPLID', 'customer.emplid', { - id: 'emplid', + }, + ), + createHeader('DoD ID', 'customer.dodID', { + id: 'dodID', + isFilterable: true, + exportValue: (row) => { + return row.customer.dodID; + }, + }), + createHeader('EMPLID', 'customer.emplid', { + id: 'emplid', + isFilterable: true, + }), + createHeader('Move code', 'locator', { + id: 'locator', + isFilterable: true, + }), + createHeader( + 'Status', + (row) => { + return row.status !== MOVE_STATUSES.SERVICE_COUNSELING_COMPLETED + ? SERVICE_COUNSELING_MOVE_STATUS_LABELS[`${row.status}`] + : null; + }, + { + id: 'status', + disableSortBy: true, + }, + ), + createHeader( + 'Requested move date', + (row) => { + return formatDateFromIso(row.requestedMoveDate, DATE_FORMAT_STRING); + }, + { + id: 'requestedMoveDate', isFilterable: true, - }), - createHeader('Move code', 'locator', { - id: 'locator', + // eslint-disable-next-line react/jsx-props-no-spreading + Filter: (props) => , + }, + ), + createHeader( + 'Date submitted', + (row) => { + return formatDateFromIso(row.submittedAt, DATE_FORMAT_STRING); + }, + { + id: 'submittedAt', isFilterable: true, - }), - createHeader( - 'Status', - (row) => { - return row.status !== MOVE_STATUSES.SERVICE_COUNSELING_COMPLETED - ? SERVICE_COUNSELING_MOVE_STATUS_LABELS[`${row.status}`] - : null; - }, - { - id: 'status', - disableSortBy: true, - }, - ), - createHeader( - 'Requested move date', - (row) => { - return formatDateFromIso(row.requestedMoveDate, DATE_FORMAT_STRING); - }, - { - id: 'requestedMoveDate', - isFilterable: true, + // eslint-disable-next-line react/jsx-props-no-spreading + Filter: (props) => , + }, + ), + createHeader( + 'Branch', + (row) => { + return serviceMemberAgencyLabel(row.customer.agency); + }, + { + id: 'branch', + isFilterable: true, + Filter: (props) => ( // eslint-disable-next-line react/jsx-props-no-spreading - Filter: (props) => , - }, - ), - createHeader( - 'Date submitted', - (row) => { - return formatDateFromIso(row.submittedAt, DATE_FORMAT_STRING); - }, - { - id: 'submittedAt', + + ), + }, + ), + createHeader('Origin GBLOC', 'originGBLOC', { + disableSortBy: true, + }), // If the user is in the USMC GBLOC they will have many different GBLOCs and will want to sort and filter + supervisor + ? createHeader( + 'Origin duty location', + (row) => { + return `${row.originDutyLocation.name}`; + }, + { + id: 'originDutyLocation', + isFilterable: true, + exportValue: (row) => { + return row.originDutyLocation?.name; + }, + Filter: (props) => ( + + ), + }, + ) + : createHeader('Origin duty location', 'originDutyLocation.name', { + id: 'originDutyLocation', isFilterable: true, - // eslint-disable-next-line react/jsx-props-no-spreading - Filter: (props) => , - }, - ), + exportValue: (row) => { + return row.originDutyLocation?.name; + }, + }), + createHeader('Counseling office', 'counselingOffice', { + id: 'counselingOffice', + isFilterable: true, + }), + isQueueManagementEnabled ?? createHeader( - 'Branch', + 'Assigned', (row) => { - return serviceMemberAgencyLabel(row.customer.agency); + const { formattedAvailableOfficeUsers, assignedToUser } = formatAvailableOfficeUsersForRow(row); + return ( +
+ handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR)} + title="Assigned dropdown" + > + {formattedAvailableOfficeUsers} + +
+ ); }, { - id: 'branch', + id: 'assignedTo', isFilterable: true, - Filter: (props) => ( - // eslint-disable-next-line react/jsx-props-no-spreading - - ), }, ), - createHeader('Origin GBLOC', 'originGBLOC', { - disableSortBy: true, - }), // If the user is in the USMC GBLOC they will have many different GBLOCs and will want to sort and filter - supervisor - ? createHeader( - 'Origin duty location', - (row) => { - return `${row.originDutyLocation.name}`; - }, - { - id: 'originDutyLocation', - isFilterable: true, - exportValue: (row) => { - return row.originDutyLocation?.name; - }, - Filter: (props) => ( - - ), - }, - ) - : createHeader('Origin duty location', 'originDutyLocation.name', { - id: 'originDutyLocation', - isFilterable: true, - }), - ]; - if (isQueueManagementEnabled) - cols.push( - createHeader( - 'Assigned', - (row) => { - const { formattedAvailableOfficeUsers, assignedToUser } = formatAvailableOfficeUsersForRow(row); - return ( -
- handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR)} - title="Assigned dropdown" - > - {formattedAvailableOfficeUsers} - -
- ); - }, - { - id: 'assignedTo', - isFilterable: true, - }, - ), - ); - - return cols; -}; +]; export const closeoutColumns = (moveLockFlag, ppmCloseoutGBLOC, ppmCloseoutOriginLocationList, supervisor) => [ createHeader( ' ', @@ -274,7 +275,7 @@ export const closeoutColumns = (moveLockFlag, ppmCloseoutGBLOC, ppmCloseoutOrigi isFilterable: true, Filter: (props) => ( // eslint-disable-next-line react/jsx-props-no-spreading - + ), }, ), diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx index b393f340d7f..d79b4b92247 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx @@ -128,6 +128,7 @@ const needsCounselingMoves = { name: 'Los Alamos', }, originGBLOC: 'LKNQ', + counselingOffice: '', assignedTo: { id: 'exampleId2', firstname: 'John', @@ -204,6 +205,7 @@ const serviceCounselingCompletedMoves = { name: 'Los Alamos', }, originGBLOC: 'LKNQ', + counselingOffice: '67592323-fc7e-4b35-83a7-57faa53b7acf', assignedTo: { id: 'exampleId1', firstname: 'Jimmy', 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..bb408d21f55 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx @@ -20,7 +20,12 @@ import formStyles from 'styles/form.module.scss'; import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; import { requiredAddressSchema, addressSchema } from 'utils/validation'; import { isEmpty, isValidWeight } from 'shared/utils'; -import { formatAddressForPrimeAPI, formatSwaggerDate, fromPrimeAPIAddressFormat } from 'utils/formatters'; +import { + formatAddressForPrimeAPI, + formatExtraAddressForPrimeAPI, + formatSwaggerDate, + fromPrimeAPIAddressFormat, +} from 'utils/formatters'; import PrimeUIShipmentUpdateForm from 'pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm'; import PrimeUIShipmentUpdatePPMForm from 'pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm'; import { setFlashMessage as setFlashMessageAction } from 'store/flash/actions'; @@ -122,9 +127,17 @@ 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 editableSecondaryPickupAddress = isEmpty(reformatPrimeApiSecondaryPickupAddress); + const editableTertiaryPickupAddress = isEmpty(reformatPrimeApiTertiaryPickupAddress); const editableDestinationAddress = isEmpty(reformatPrimeApiDestinationAddress); + const editableSecondaryDeliveryAddress = isEmpty(reformatPrimeApiSecondaryDeliveryAddress); + const editableTertiaryDeliveryAddress = isEmpty(reformatPrimeApiTertiaryDeliveryAddress); const onCancelShipmentClick = () => { mutateMTOShipmentStatus({ mtoShipmentID: shipmentId, ifMatchETag: shipment.eTag }).then(() => { @@ -140,8 +153,10 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { expectedDepartureDate, pickupAddress, secondaryPickupAddress, + tertiaryPickupAddress, destinationAddress, secondaryDestinationAddress, + tertiaryDestinationAddress, sitExpected, sitLocation, sitEstimatedWeight, @@ -152,7 +167,9 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { proGearWeight, spouseProGearWeight, hasSecondaryPickupAddress, + hasTertiaryPickupAddress, hasSecondaryDestinationAddress, + hasTertiaryDestinationAddress, }, counselorRemarks, } = values; @@ -163,10 +180,16 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { secondaryPickupAddress: isEmpty(secondaryPickupAddress) ? emptyAddress : formatAddressForPrimeAPI(secondaryPickupAddress), + tertiaryPickupAddress: isEmpty(tertiaryPickupAddress) + ? emptyAddress + : formatAddressForPrimeAPI(tertiaryPickupAddress), destinationAddress: isEmpty(destinationAddress) ? null : formatAddressForPrimeAPI(destinationAddress), secondaryDestinationAddress: isEmpty(secondaryDestinationAddress) ? emptyAddress : formatAddressForPrimeAPI(secondaryDestinationAddress), + tertiaryDestinationAddress: isEmpty(tertiaryDestinationAddress) + ? emptyAddress + : formatAddressForPrimeAPI(tertiaryDestinationAddress), sitExpected, ...(sitExpected && { sitLocation: sitLocation || null, @@ -181,7 +204,9 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { spouseProGearWeight: spouseProGearWeight ? parseInt(spouseProGearWeight, 10) : null, }), hasSecondaryPickupAddress: hasSecondaryPickupAddress === 'true', + hasTertiaryPickupAddress: hasTertiaryPickupAddress === 'true', hasSecondaryDestinationAddress: hasSecondaryDestinationAddress === 'true', + hasTertiaryDestinationAddress: hasTertiaryDestinationAddress === 'true', }, counselorRemarks: counselorRemarks || null, }; @@ -196,7 +221,11 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { actualDeliveryDate, scheduledDeliveryDate, pickupAddress, + secondaryPickupAddress, + tertiaryPickupAddress, destinationAddress, + secondaryDeliveryAddress, + tertiaryDeliveryAddress, destinationType, diversion, } = values; @@ -211,7 +240,19 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { scheduledDeliveryDate: scheduledDeliveryDate ? formatSwaggerDate(scheduledDeliveryDate) : null, actualDeliveryDate: actualDeliveryDate ? formatSwaggerDate(actualDeliveryDate) : null, pickupAddress: editablePickupAddress ? formatAddressForPrimeAPI(pickupAddress) : null, + secondaryPickupAddress: editableSecondaryPickupAddress + ? formatExtraAddressForPrimeAPI(secondaryPickupAddress) + : null, + tertiaryPickupAddress: editableTertiaryPickupAddress + ? formatExtraAddressForPrimeAPI(tertiaryPickupAddress) + : null, destinationAddress: editableDestinationAddress ? formatAddressForPrimeAPI(destinationAddress) : null, + secondaryDeliveryAddress: editableSecondaryDeliveryAddress + ? formatExtraAddressForPrimeAPI(secondaryDeliveryAddress) + : null, + tertiaryDeliveryAddress: editableTertiaryDeliveryAddress + ? formatExtraAddressForPrimeAPI(tertiaryDeliveryAddress) + : null, destinationType, diversion, }; @@ -233,12 +274,18 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { secondaryPickupAddress: shipment.ppmShipment.secondaryPickupAddress ? formatAddressForPrimeAPI(shipment.ppmShipment.secondaryPickupAddress) : emptyAddress, + tertiaryPickupAddress: shipment.ppmShipment.tertiaryPickupAddress + ? formatAddressForPrimeAPI(shipment.ppmShipment.tertiaryPickupAddress) + : emptyAddress, destinationAddress: shipment.ppmShipment.destinationAddress ? formatAddressForPrimeAPI(shipment.ppmShipment.destinationAddress) : emptyAddress, secondaryDestinationAddress: shipment.ppmShipment.secondaryDestinationAddress ? formatAddressForPrimeAPI(shipment.ppmShipment.secondaryDestinationAddress) : emptyAddress, + tertiaryDestinationAddress: shipment.ppmShipment.tertiaryDestinationAddress + ? formatAddressForPrimeAPI(shipment.ppmShipment.tertiaryDestinationAddress) + : emptyAddress, sitExpected: shipment.ppmShipment.sitExpected, sitLocation: shipment.ppmShipment.sitLocation, sitEstimatedWeight: shipment.ppmShipment.sitEstimatedWeight?.toString(), @@ -250,7 +297,9 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { proGearWeight: shipment.ppmShipment.proGearWeight?.toString(), spouseProGearWeight: shipment.ppmShipment.spouseProGearWeight?.toString(), hasSecondaryPickupAddress: shipment.ppmShipment.hasSecondaryPickupAddress ? 'true' : 'false', + hasTertiaryPickupAddress: shipment.ppmShipment.hasTertiaryPickupAddress ? 'true' : 'false', hasSecondaryDestinationAddress: shipment.ppmShipment.hasSecondaryDestinationAddress ? 'true' : 'false', + hasTertiaryDestinationAddress: shipment.ppmShipment.hasTertiaryDestinationAddress ? 'true' : 'false', }, counselorRemarks: shipment.counselorRemarks || '', }; @@ -261,8 +310,10 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { .typeError('Invalid date. Must be in the format: DD MMM YYYY'), pickupAddress: requiredAddressSchema.required('Required'), secondaryPickupAddress: OptionalAddressSchema, + tertiaryPickupAddress: OptionalAddressSchema, destinationAddress: requiredAddressSchema.required('Required'), secondaryDestinationAddress: OptionalAddressSchema, + tertiaryDestinationAddress: OptionalAddressSchema, sitExpected: Yup.boolean().required('Required'), sitLocation: Yup.string().when('sitExpected', { is: true, @@ -309,7 +360,13 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { scheduledDeliveryDate: shipment.scheduledDeliveryDate, actualDeliveryDate: shipment.actualDeliveryDate, pickupAddress: editablePickupAddress ? emptyAddress : reformatPrimeApiPickupAddress, + secondaryPickupAddress: editableSecondaryPickupAddress ? emptyAddress : reformatPrimeApiSecondaryPickupAddress, + tertiaryPickupAddress: editableTertiaryPickupAddress ? emptyAddress : reformatPrimeApiTertiaryPickupAddress, destinationAddress: editableDestinationAddress ? emptyAddress : reformatPrimeApiDestinationAddress, + secondaryDeliveryAddress: editableSecondaryDeliveryAddress + ? emptyAddress + : reformatPrimeApiSecondaryDeliveryAddress, + tertiaryDeliveryAddress: editableTertiaryDeliveryAddress ? emptyAddress : reformatPrimeApiTertiaryDeliveryAddress, destinationType: shipment.destinationType, diversion: shipment.diversion, }; @@ -357,15 +414,24 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { editableProGearWeightActualField={editableProGearWeightActualField} editableSpouseProGearWeightActualField={editableSpouseProGearWeightActualField} editablePickupAddress={editablePickupAddress} + editableSecondaryPickupAddress={editableSecondaryPickupAddress} + editableTertiaryPickupAddress={editableTertiaryPickupAddress} editableDestinationAddress={editableDestinationAddress} + editableSecondaryDeliveryAddress={editableSecondaryDeliveryAddress} + editableTertiaryDeliveryAddress={editableTertiaryDeliveryAddress} estimatedWeight={initialValues.estimatedWeight} actualWeight={initialValues.actualWeight} actualProGearWeight={initialValues.actualProGearWeight} 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} + shipmentType={shipment.shipmentType} /> )}
diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx index ee1bf418c34..0d234eb1136 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.jsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import { useNavigate, useParams, generatePath } from 'react-router-dom'; +import { useNavigate, useParams, generatePath, useLocation } from 'react-router-dom'; import { useQueryClient, useMutation } from '@tanstack/react-query'; import { Alert, Grid, GridContainer } from '@trussworks/react-uswds'; import * as Yup from 'yup'; @@ -11,26 +11,24 @@ import { usePrimeSimulatorGetMove } from 'hooks/queries'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; import SomethingWentWrong from 'shared/SomethingWentWrong'; import { primeSimulatorRoutes } from 'constants/routes'; -import { addressSchema } from 'utils/validation'; +import { ZIP_CODE_REGEX } from 'utils/validation'; import scrollToTop from 'shared/scrollToTop'; import { updatePrimeMTOShipmentAddress } from 'services/primeApi'; import primeStyles from 'pages/PrimeUI/Prime.module.scss'; import { isEmpty } from 'shared/utils'; import { fromPrimeAPIAddressFormat } from 'utils/formatters'; import { PRIME_SIMULATOR_MOVE } from 'constants/queryKeys'; +import { getAddressLabel } from 'shared/constants'; -const updatePickupAddressSchema = Yup.object().shape({ +const updateAddressSchema = Yup.object().shape({ addressID: Yup.string(), - pickupAddress: Yup.object().shape({ - address: addressSchema, - }), - eTag: Yup.string(), -}); - -const updateDestinationAddressSchema = Yup.object().shape({ - addressID: Yup.string(), - destinationAddress: Yup.object().shape({ - address: addressSchema, + address: Yup.object().shape({ + id: Yup.string(), + streetAddress1: Yup.string().required('Required'), + streetAddress2: Yup.string(), + city: Yup.string().required('Required'), + state: Yup.string().required('Required').length(2, 'Must use state abbreviation'), + postalCode: Yup.string().required('Required').matches(ZIP_CODE_REGEX, 'Must be valid zip code'), }), eTag: Yup.string(), }); @@ -42,6 +40,11 @@ const PrimeUIShipmentUpdateAddress = () => { const mtoShipments = moveTaskOrder?.mtoShipments; const shipment = mtoShipments?.find((mtoShipment) => mtoShipment?.id === shipmentId); const navigate = useNavigate(); + const location = useLocation(); + + const addressType = location?.state?.addressType; + const addressLabel = getAddressLabel(addressType); + const addressData = shipment ? shipment[addressType] : null; const handleClose = () => { navigate(generatePath(primeSimulatorRoutes.VIEW_MOVE_PATH, { moveCodeOrID })); @@ -52,12 +55,10 @@ const PrimeUIShipmentUpdateAddress = () => { onSuccess: (updatedMTOShipmentAddress) => { const shipmentIndex = mtoShipments.findIndex((mtoShipment) => mtoShipment.id === shipmentId); let updateQuery = false; - ['pickupAddress', 'destinationAddress'].forEach((key) => { - if (updatedMTOShipmentAddress.id === mtoShipments[shipmentIndex][key].id) { - mtoShipments[shipmentIndex][key] = updatedMTOShipmentAddress; - updateQuery = true; - } - }); + if (updatedMTOShipmentAddress.id === mtoShipments[shipmentIndex][addressType].id) { + mtoShipments[shipmentIndex][addressType] = updatedMTOShipmentAddress; + updateQuery = true; + } if (updateQuery) { moveTaskOrder.mtoShipments = mtoShipments; queryClient.setQueryData([PRIME_SIMULATOR_MOVE, moveCodeOrID], moveTaskOrder); @@ -87,19 +88,16 @@ const PrimeUIShipmentUpdateAddress = () => { if (isError) return ; const onSubmit = (values, { setSubmitting }) => { - // Choose pickupAddress or destinationAddress by the presence of the object - // by the same name. It's possible that these values are blank and set to - // `undefined` or an empty string `""`. - const address = values.pickupAddress ? values.pickupAddress.address : values.destinationAddress.address; + const { streetAddress1, streetAddress2, streetAddress3, city, state, postalCode } = values.address; const body = { id: values.addressID, - streetAddress1: address.streetAddress1, - streetAddress2: address.streetAddress2, - streetAddress3: address.streetAddress3, - city: address.city, - state: address.state, - postalCode: address.postalCode, + streetAddress1, + streetAddress2, + streetAddress3, + city, + state, + postalCode, }; // Check if the address payload contains any blank properties and remove @@ -121,24 +119,13 @@ const PrimeUIShipmentUpdateAddress = () => { }); }; - const reformatPrimeApiPickupAddress = fromPrimeAPIAddressFormat(shipment.pickupAddress); - const reformatPrimeApiDestinationAddress = fromPrimeAPIAddressFormat(shipment.destinationAddress); - const editablePickupAddress = !isEmpty(reformatPrimeApiPickupAddress); - const editableDestinationAddress = !isEmpty(reformatPrimeApiDestinationAddress); + const reformatPriApiAddress = fromPrimeAPIAddressFormat(addressData); + const editableAddress = !isEmpty(reformatPriApiAddress); - const initialValuesPickupAddress = { - addressID: shipment.pickupAddress?.id, - pickupAddress: { - address: reformatPrimeApiPickupAddress, - }, - eTag: shipment.pickupAddress?.eTag, - }; - const initialValuesDestinationAddress = { - addressID: shipment.destinationAddress?.id, - destinationAddress: { - address: reformatPrimeApiDestinationAddress, - }, - eTag: shipment.destinationAddress?.eTag, + const initialValues = { + addressID: addressData?.id, + address: reformatPriApiAddress, + eTag: addressData?.eTag, }; return ( @@ -155,23 +142,14 @@ const PrimeUIShipmentUpdateAddress = () => {
)} -

Update Existing Pickup & Destination Address

- {editablePickupAddress && ( - - )} - {editableDestinationAddress && ( +

Update Existing {`${addressLabel}`}

+ {editableAddress && ( )} diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx index 3f0f87a8e67..5e2083a7fd7 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddress.test.jsx @@ -15,6 +15,7 @@ const mockNavigate = jest.fn(); jest.mock('react-router-dom', () => ({ ...jest.requireActual('react-router-dom'), useNavigate: () => mockNavigate, + useLocation: () => ({ state: { addressType: 'pickupAddress' } }), })); jest.mock('hooks/queries', () => ({ @@ -72,54 +73,6 @@ const moveReturnValue = { isError: false, }; -const noDestinationAddressReturnValue = { - moveTaskOrder: { - id: '1', - moveCode: 'LN4T89', - mtoShipments: [ - { - id: '4', - shipmentType: 'HHG', - requestedPickupDate: '2021-12-01', - pickupAddress: { - id: '1', - streetAddress1: '800 Madison Avenue', - city: 'New York', - state: 'NY', - postalCode: '10002', - }, - destinationAddress: null, - }, - ], - }, - isLoading: false, - isError: false, -}; - -const noPickupAddressReturnValue = { - moveTaskOrder: { - id: '1', - moveCode: 'LN4T89', - mtoShipments: [ - { - id: '4', - shipmentType: 'HHG', - requestedPickupDate: '2021-12-01', - pickupAddress: null, - destinationAddress: { - id: '2', - streetAddress1: '100 1st Avenue', - city: 'New York', - state: 'NY', - postalCode: '10001', - }, - }, - ], - }, - isLoading: false, - isError: false, -}; - const renderComponent = () => { render( @@ -168,7 +121,7 @@ describe('PrimeUIShipmentUpdateAddress page', () => { renderComponent(); const pageHeading = await screen.getByRole('heading', { - name: 'Update Existing Pickup & Destination Address', + name: 'Update Existing Pickup Address', level: 1, }); expect(pageHeading).toBeInTheDocument(); @@ -179,74 +132,11 @@ describe('PrimeUIShipmentUpdateAddress page', () => { const shipment = moveTaskOrder.mtoShipments[shipmentIndex]; await waitFor(() => { - expect(screen.getByRole('heading', { name: /Pickup address/, level: 2 })); expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue(shipment.pickupAddress.streetAddress1); expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); expect(screen.getAllByLabelText('City')[0]).toHaveValue(shipment.pickupAddress.city); expect(screen.getAllByLabelText('State')[0]).toHaveValue(shipment.pickupAddress.state); expect(screen.getAllByLabelText('ZIP')[0]).toHaveValue(shipment.pickupAddress.postalCode); - expect(screen.getByRole('heading', { name: /Destination address/, level: 2 })); - expect(screen.getAllByLabelText('Address 1')[1]).toHaveValue(shipment.destinationAddress.streetAddress1); - expect(screen.getAllByLabelText(/Address 2/)[1]).toHaveValue(''); - expect(screen.getAllByLabelText('City')[1]).toHaveValue(shipment.destinationAddress.city); - expect(screen.getAllByLabelText('State')[1]).toHaveValue(shipment.destinationAddress.state); - expect(screen.getAllByLabelText('ZIP')[1]).toHaveValue(shipment.destinationAddress.postalCode); - }); - }); - - it('displays only pickup address form', async () => { - usePrimeSimulatorGetMove.mockReturnValue(noDestinationAddressReturnValue); - - renderComponent(); - - const pageHeading = await screen.getByRole('heading', { - name: 'Update Existing Pickup & Destination Address', - level: 1, - }); - expect(pageHeading).toBeInTheDocument(); - - const shipmentIndex = noDestinationAddressReturnValue.moveTaskOrder.mtoShipments.findIndex( - (mtoShipment) => mtoShipment.id === routingParams.shipmentId, - ); - const shipment = noDestinationAddressReturnValue.moveTaskOrder.mtoShipments[shipmentIndex]; - - await waitFor(() => { - expect(screen.getByRole('heading', { name: /Pickup address/, level: 2 })); - expect(screen.getAllByLabelText('Address 1').length).toBe(1); - expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue(shipment.pickupAddress.streetAddress1); - expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); - expect(screen.getAllByLabelText('City')[0]).toHaveValue(shipment.pickupAddress.city); - expect(screen.getAllByLabelText('State')[0]).toHaveValue(shipment.pickupAddress.state); - expect(screen.getAllByLabelText('ZIP')[0]).toHaveValue(shipment.pickupAddress.postalCode); - expect(shipment.destinationAddress).toBeNull(); - }); - }); - - it('displays only destination address form', async () => { - usePrimeSimulatorGetMove.mockReturnValue(noPickupAddressReturnValue); - - renderComponent(); - - const pageHeading = await screen.getByRole('heading', { - name: 'Update Existing Pickup & Destination Address', - level: 1, - }); - expect(pageHeading).toBeInTheDocument(); - - const shipmentIndex = noPickupAddressReturnValue.moveTaskOrder.mtoShipments.findIndex( - (mtoShipment) => mtoShipment.id === routingParams.shipmentId, - ); - const shipment = noPickupAddressReturnValue.moveTaskOrder.mtoShipments[shipmentIndex]; - - await waitFor(() => { - expect(shipment.pickupAddress).toBeNull(); - expect(screen.getByRole('heading', { name: /Destination address/, level: 2 })); - expect(screen.getAllByLabelText('Address 1').length).toBe(1); - expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue(shipment.destinationAddress.streetAddress1); - expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); - expect(screen.getAllByLabelText('City')[0]).toHaveValue(shipment.destinationAddress.city); - expect(screen.getAllByLabelText('State')[0]).toHaveValue(shipment.destinationAddress.state); - expect(screen.getAllByLabelText('ZIP')[0]).toHaveValue(shipment.destinationAddress.postalCode); }); }); }); @@ -304,7 +194,7 @@ describe('PrimeUIShipmentUpdateAddress page', () => { renderComponent(); await act(async () => { - expect(screen.getAllByRole('button', { name: 'Save' }).length).toBe(2); + expect(screen.getAllByRole('button', { name: 'Save' }).length).toBe(1); await userEvent.click(screen.getAllByRole('button', { name: 'Save' })[0]); }); diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddressForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddressForm.jsx index 7045a671fb0..ec188502922 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddressForm.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateAddressForm.jsx @@ -54,12 +54,7 @@ const PrimeUIShipmentUpdateAddressForm = ({ PrimeUIShipmentUpdateAddressForm.propTypes = { initialValues: PropTypes.shape({ - pickupAddress: PropTypes.shape({ - address: ResidentialAddressShape, - }), - destinationAddress: PropTypes.shape({ - address: ResidentialAddressShape, - }), + address: ResidentialAddressShape, addressID: PropTypes.string, eTag: PropTypes.string, }).isRequired, @@ -69,7 +64,7 @@ PrimeUIShipmentUpdateAddressForm.propTypes = { addressID: PropTypes.string, eTag: PropTypes.string, }).isRequired, - addressLocation: PropTypes.oneOf(['Pickup address', 'Destination address']).isRequired, + addressLocation: PropTypes.string.isRequired, name: PropTypes.string.isRequired, }; diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx index 0c71eb62549..795e6caf525 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx @@ -11,6 +11,7 @@ import { AddressFields } from 'components/form/AddressFields/AddressFields'; import SectionWrapper from 'components/Customer/SectionWrapper'; import formStyles from 'styles/form.module.scss'; import { shipmentDestinationTypes } from 'constants/shipments'; +import { SHIPMENT_OPTIONS } from 'shared/constants'; const emptyAddressShape = { streetAddress1: '', @@ -28,15 +29,26 @@ const PrimeUIShipmentUpdateForm = ({ editableProGearWeightActualField, editableSpouseProGearWeightActualField, editablePickupAddress, + editableSecondaryPickupAddress, + editableTertiaryPickupAddress, editableDestinationAddress, + editableSecondaryDeliveryAddress, + editableTertiaryDeliveryAddress, requestedPickupDate, estimatedWeight, actualWeight, actualProGearWeight, actualSpouseProGearWeight, pickupAddress, + secondaryPickupAddress, + tertiaryPickupAddress, destinationAddress, + secondaryDeliveryAddress, + tertiaryDeliveryAddress, + shipmentType, }) => { + const isNTS = shipmentType === SHIPMENT_OPTIONS.NTS; + const isNTSR = shipmentType === SHIPMENT_OPTIONS.NTSR; return (

Shipment Dates

@@ -142,9 +154,29 @@ const PrimeUIShipmentUpdateForm = ({
Pickup Address
{editablePickupAddress && } {!editablePickupAddress && formatAddress(pickupAddress)} + {!isNTSR && ( + <> +
Second Pickup Address
+ {editableSecondaryPickupAddress && } + {!editableSecondaryPickupAddress && formatAddress(secondaryPickupAddress)} +
Third Pickup Address
+ {editableTertiaryPickupAddress && } + {!editableTertiaryPickupAddress && formatAddress(tertiaryPickupAddress)} + + )}
Destination Address
{editableDestinationAddress && } {!editableDestinationAddress && formatAddress(destinationAddress)} + {!isNTS && ( + <> +
Second Delivery Address
+ {editableSecondaryDeliveryAddress && } + {!editableSecondaryDeliveryAddress && formatAddress(secondaryDeliveryAddress)} +
Third Delivery Address
+ {editableTertiaryDeliveryAddress && } + {!editableTertiaryDeliveryAddress && formatAddress(tertiaryDeliveryAddress)} + + )} { const { values } = useFormikContext(); - const { sitExpected, hasProGear, hasSecondaryPickupAddress, hasSecondaryDestinationAddress } = values.ppmShipment; + const { + sitExpected, + hasProGear, + hasSecondaryPickupAddress, + hasTertiaryPickupAddress, + hasSecondaryDestinationAddress, + hasTertiaryDestinationAddress, + } = values.ppmShipment; return ( @@ -62,7 +69,41 @@ const PrimeUIShipmentUpdatePPMForm = () => { />
- {hasSecondaryPickupAddress === 'true' && } + {hasSecondaryPickupAddress === 'true' && ( + <> + +

Third pickup location

+ +

+ Will the movers pick up any belongings from a third address? (Must be near the pickup address. + Subject to approval.) +

+
+ + +
+
+ {hasTertiaryPickupAddress === 'true' && } + + )} )} /> @@ -103,7 +144,41 @@ const PrimeUIShipmentUpdatePPMForm = () => {
{hasSecondaryDestinationAddress === 'true' && ( - + <> + +

Third destination location

+ +

+ Will the movers pick up any belongings from a third address? (Must be near the Destination address. + Subject to approval.) +

+
+ + +
+
+ {hasTertiaryDestinationAddress === 'true' && ( + + )} + )} )} diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.test.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.test.jsx index e6ca000f04a..be3ea189cd7 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.test.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdatePPMForm.test.jsx @@ -69,6 +69,14 @@ const shipment = { state: 'KY', postalCode: '42702', }, + tertiaryPickupAddress: { + streetAddress1: '123 Test Lane', + streetAddress2: '234 Test Lane', + streetAddress3: 'Test Woman', + city: 'Missoula', + state: 'MT', + postalCode: '59801', + }, destinationAddress: { streetAddress1: '222 Test Street', streetAddress2: '333 Test Street', @@ -85,8 +93,18 @@ const shipment = { state: 'KY', postalCode: '42701', }, + tertiaryDestinationAddress: { + streetAddress1: '321 Test Lane', + streetAddress2: '432 Test Lane', + streetAddress3: 'Test Woman', + city: 'Silver Spring', + state: 'MD', + postalCode: '20906', + }, hasSecondaryPickupAddress: 'true', hasSecondaryDestinationAddress: 'true', + hasTertiaryPickupAddress: 'true', + hasTertiaryDestinationAddress: 'true', submittedAt: '2022-07-01T13:41:33.252Z', updatedAt: '2022-07-01T14:23:19.780Z', }, @@ -106,6 +124,18 @@ const shipment = { state: null, streetAddress1: null, }, + tertiaryDeliveryAddress: { + city: null, + postalCode: null, + state: null, + streetAddress1: null, + }, + tertiaryPickupAddress: { + city: null, + postalCode: null, + state: null, + streetAddress1: null, + }, shipmentType: 'PPM', status: 'APPROVED', updatedAt: '2022-07-01T14:23:19.738Z', @@ -173,33 +203,63 @@ describe('PrimeUIShipmentUpdatePPMForm', () => { ); expect(await screen.getAllByLabelText('Address 1')[2]).toHaveValue( - initialValues.ppmShipment.destinationAddress.streetAddress1, + initialValues.ppmShipment.tertiaryPickupAddress.streetAddress1, ); expect(await screen.getAllByLabelText(/Address 2/)[2]).toHaveValue( - initialValues.ppmShipment.destinationAddress.streetAddress2, + initialValues.ppmShipment.tertiaryPickupAddress.streetAddress2, + ); + expect(await screen.getAllByLabelText('City')[2]).toHaveValue(initialValues.ppmShipment.tertiaryPickupAddress.city); + expect(await screen.getAllByLabelText('State')[2]).toHaveValue( + initialValues.ppmShipment.tertiaryPickupAddress.state, ); - expect(await screen.getAllByLabelText('City')[2]).toHaveValue(initialValues.ppmShipment.destinationAddress.city); - expect(await screen.getAllByLabelText('State')[2]).toHaveValue(initialValues.ppmShipment.destinationAddress.state); expect(await screen.getAllByLabelText('ZIP')[2]).toHaveValue( - initialValues.ppmShipment.destinationAddress.postalCode, + initialValues.ppmShipment.tertiaryPickupAddress.postalCode, ); expect(await screen.getAllByLabelText('Address 1')[3]).toHaveValue( - initialValues.ppmShipment.secondaryDestinationAddress.streetAddress1, + initialValues.ppmShipment.destinationAddress.streetAddress1, ); expect(await screen.getAllByLabelText(/Address 2/)[3]).toHaveValue( + initialValues.ppmShipment.destinationAddress.streetAddress2, + ); + expect(await screen.getAllByLabelText('City')[3]).toHaveValue(initialValues.ppmShipment.destinationAddress.city); + expect(await screen.getAllByLabelText('State')[3]).toHaveValue(initialValues.ppmShipment.destinationAddress.state); + expect(await screen.getAllByLabelText('ZIP')[3]).toHaveValue( + initialValues.ppmShipment.destinationAddress.postalCode, + ); + + expect(await screen.getAllByLabelText('Address 1')[4]).toHaveValue( + initialValues.ppmShipment.secondaryDestinationAddress.streetAddress1, + ); + expect(await screen.getAllByLabelText(/Address 2/)[4]).toHaveValue( initialValues.ppmShipment.secondaryDestinationAddress.streetAddress2, ); - expect(await screen.getAllByLabelText('City')[3]).toHaveValue( + expect(await screen.getAllByLabelText('City')[4]).toHaveValue( initialValues.ppmShipment.secondaryDestinationAddress.city, ); - expect(await screen.getAllByLabelText('State')[3]).toHaveValue( + expect(await screen.getAllByLabelText('State')[4]).toHaveValue( initialValues.ppmShipment.secondaryDestinationAddress.state, ); - expect(await screen.getAllByLabelText('ZIP')[3]).toHaveValue( + expect(await screen.getAllByLabelText('ZIP')[4]).toHaveValue( initialValues.ppmShipment.secondaryDestinationAddress.postalCode, ); + expect(await screen.getAllByLabelText('Address 1')[5]).toHaveValue( + initialValues.ppmShipment.tertiaryDestinationAddress.streetAddress1, + ); + expect(await screen.getAllByLabelText(/Address 2/)[5]).toHaveValue( + initialValues.ppmShipment.tertiaryDestinationAddress.streetAddress2, + ); + expect(await screen.getAllByLabelText('City')[5]).toHaveValue( + initialValues.ppmShipment.tertiaryDestinationAddress.city, + ); + expect(await screen.getAllByLabelText('State')[5]).toHaveValue( + initialValues.ppmShipment.tertiaryDestinationAddress.state, + ); + expect(await screen.getAllByLabelText('ZIP')[5]).toHaveValue( + initialValues.ppmShipment.tertiaryDestinationAddress.postalCode, + ); + expect(await screen.findByText('Storage In Transit (SIT)')).toBeInTheDocument(); expect(await screen.findByLabelText('SIT Expected')).toBeChecked(); expect(await screen.findByLabelText('SIT Location')).toHaveValue(initialValues.ppmShipment.sitLocation); diff --git a/src/services/ghcApi.js b/src/services/ghcApi.js index ea7399a06c9..15a9666bd32 100644 --- a/src/services/ghcApi.js +++ b/src/services/ghcApi.js @@ -848,6 +848,10 @@ export async function patchPPMSIT({ ppmShipmentId, payload, eTag }) { ); } +export async function bulkDownloadPaymentRequest(paymentRequestID) { + return makeGHCRequestRaw('paymentRequests.bulkDownload', { paymentRequestID }); +} + export async function dateSelectionIsWeekendHoliday(countryCode, date) { return makeGHCRequestRaw( 'calendar.isDateWeekendHoliday', diff --git a/src/shared/constants.js b/src/shared/constants.js index 4fb0ab134a8..7852275dc1b 100644 --- a/src/shared/constants.js +++ b/src/shared/constants.js @@ -198,3 +198,23 @@ export const MOVE_DOCUMENT_TYPE = { AMENDMENTS: 'AMENDMENTS', SUPPORTING: 'SUPPORTING', }; + +export const ADDRESS_TYPES = { + PICKUP: 'pickupAddress', + SECOND_PICKUP: 'secondaryPickupAddress', + THIRD_PICKUP: 'tertiaryPickupAddress', + DESTINATION: 'destinationAddress', + SECOND_DESTINATION: 'secondaryDeliveryAddress', + THIRD_DESTINATION: 'tertiaryDeliveryAddress', +}; + +const ADDRESS_LABELS_MAP = { + [ADDRESS_TYPES.PICKUP]: 'Pickup Address', + [ADDRESS_TYPES.SECOND_PICKUP]: 'Second Pickup Address', + [ADDRESS_TYPES.THIRD_PICKUP]: 'Third Pickup Address', + [ADDRESS_TYPES.DESTINATION]: 'Destination Address', + [ADDRESS_TYPES.SECOND_DESTINATION]: 'Second Destination Address', + [ADDRESS_TYPES.THIRD_DESTINATION]: 'Third Destination Address', +}; + +export const getAddressLabel = (type) => ADDRESS_LABELS_MAP[type]; diff --git a/src/shared/styles/colors.scss b/src/shared/styles/colors.scss index 4914ff82a5b..15fb42e6b02 100644 --- a/src/shared/styles/colors.scss +++ b/src/shared/styles/colors.scss @@ -19,8 +19,8 @@ $base-darker: #3d4551; $base-darkest: #171717; //Group 4: Form State colors -$error: #d63e04; -$error-dark: #A03003; +$error: #e34b11; +$error-dark: #a03003; $error-light: #f4e3db; $warning: #ffbe2e; $warning-light: #faf3d1; @@ -38,7 +38,7 @@ $accent-ub: #f2938c; $accent-nts: #d85bef; $accent-ntsr: #8168b3; $accent-boat: #5d92ba; -$accent-pro-gear: #71767A; +$accent-pro-gear: #71767a; $accent-default: $base-light; // Group 6 Info colors diff --git a/src/utils/customer.js b/src/utils/customer.js index b8d54cf2b0a..af84bb80de7 100644 --- a/src/utils/customer.js +++ b/src/utils/customer.js @@ -23,3 +23,28 @@ export const findNextServiceMemberStep = (profileState) => { 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/src/utils/formatters.js b/src/utils/formatters.js index 139ef5b179f..50b54459236 100644 --- a/src/utils/formatters.js +++ b/src/utils/formatters.js @@ -406,6 +406,19 @@ export const formatAddressForPrimeAPI = (address) => { }; }; +export const formatExtraAddressForPrimeAPI = (address) => { + const { streetAddress1, city, state, postalCode } = address; + if (streetAddress1 === '' || city === '' || state === '' || postalCode === '') return null; + return { + streetAddress1: address.streetAddress1, + streetAddress2: address.streetAddress2, + streetAddress3: address.streetAddress3, + city: address.city, + state: address.state, + postalCode: address.postalCode, + }; +}; + const emptyAddress = { streetAddress1: '', streetAddress2: '', diff --git a/swagger-def/definitions/PPMShipmentStatus.yaml b/swagger-def/definitions/PPMShipmentStatus.yaml index cd9a4aed306..c4bff9288f0 100644 --- a/swagger-def/definitions/PPMShipmentStatus.yaml +++ b/swagger-def/definitions/PPMShipmentStatus.yaml @@ -15,3 +15,4 @@ enum: - NEEDS_ADVANCE_APPROVAL - NEEDS_CLOSEOUT - CLOSEOUT_COMPLETE + - CANCELED \ No newline at end of file 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 37fbcccd544..46a0079fc7b 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -368,6 +368,42 @@ paths: description: Returns counseling evaluation reports for the specified move that are visible to the current office user summary: Returns counseling evaluation reports for the specified move that are visible to the current office user operationId: getMoveCounselingEvaluationReportsList + '/moves/{moveID}/cancel': + parameters: + - description: ID of the move + in: path + name: moveID + required: true + format: uuid + type: string + post: + consumes: + - application/json + produces: + - application/json + parameters: [] + responses: + '200': + description: Successfully cancelled move + schema: + $ref: '#/definitions/Move' + '403': + $ref: '#/responses/PermissionDenied' + '404': + $ref: '#/responses/NotFound' + '409': + $ref: '#/responses/Conflict' + '412': + $ref: '#/responses/PreconditionFailed' + '422': + $ref: '#/responses/UnprocessableEntity' + '500': + $ref: '#/responses/ServerError' + tags: + - move + description: cancels a move + operationId: moveCanceler + summary: Cancels a move '/counseling/orders/{orderID}': parameters: - description: ID of order to update @@ -3105,6 +3141,36 @@ paths: summary: Updates status of a payment request by id x-permissions: - update.paymentRequest + '/payment-requests/{paymentRequestID}/bulkDownload': + parameters: + - description: the id for the payment-request with files to be downloaded + in: path + name: paymentRequestID + required: true + type: string + get: + summary: Downloads all Payment Request documents as a PDF + description: | + This endpoint downloads all uploaded payment request documentation combined into a single PDF. + operationId: bulkDownload + tags: + - paymentRequests + produces: + - application/pdf + responses: + '200': + headers: + Content-Disposition: + type: string + description: File name to download + description: Payment Request Files PDF + schema: + format: binary + type: file + '400': + $ref: '#/responses/InvalidRequest' + '500': + $ref: '#/responses/ServerError' /documents/{documentId}: get: summary: Returns a document @@ -3201,6 +3267,7 @@ paths: closeoutInitiated, closeoutLocation, ppmStatus, + counselingOffice, assignedTo, ] description: field that results should be sorted by @@ -3221,6 +3288,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 @@ -3763,8 +3834,8 @@ paths: get: produces: - application/json - summary: Returns the transportation offices matching the search query - description: Returns the transportation offices matching the search query + summary: Returns the transportation offices matching the search query that is enabled for PPM closeout + description: Returns the transportation offices matching the search query that is enabled for PPM closeout operationId: getTransportationOffices tags: - transportationOffice @@ -4407,7 +4478,7 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 - dodID: + edipi: type: string userID: type: string @@ -4546,8 +4617,9 @@ definitions: $ref: 'definitions/Affiliation.yaml' edipi: type: string - example: John - x-nullable: true + example: '1234567890' + maxLength: 10 + x-nullable: false emplid: type: string example: '9485155' @@ -6711,6 +6783,9 @@ definitions: ppmStatus: $ref: '#/definitions/PPMStatus' x-nullable: true + counselingOffice: + type: string + x-nullable: true assignedTo: $ref: '#/definitions/AssignedOfficeUser' x-nullable: true 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-def/prime_v3.yaml b/swagger-def/prime_v3.yaml index ebefad72192..9a28d0380e2 100644 --- a/swagger-def/prime_v3.yaml +++ b/swagger-def/prime_v3.yaml @@ -561,6 +561,15 @@ definitions: An optional secondary pickup location near the origin where additional goods exist. allOf: - $ref: 'definitions/Address.yaml' + hasTertiaryPickupAddress: + type: boolean + x-omitempty: false + x-nullable: true + tertiaryPickupAddress: + description: > + An optional third pickup location near the origin where additional goods exist. + allOf: + - $ref: 'definitions/Address.yaml' destinationAddress: description: > The address of the destination location where goods are being delivered to. @@ -575,6 +584,15 @@ definitions: An optional secondary address near the destination where goods will be dropped off. allOf: - $ref: 'definitions/Address.yaml' + hasTertiaryDestinationAddress: + type: boolean + x-omitempty: false + x-nullable: true + tertiaryDestinationAddress: + description: > + An optional third address near the destination where goods will be dropped off. + allOf: + - $ref: 'definitions/Address.yaml' sitExpected: description: | Captures whether some or all of the PPM shipment will require temporary storage at the origin or destination. @@ -709,6 +727,14 @@ definitions: description: A second delivery address for this shipment, if the customer entered one. An optional field. allOf: - $ref: 'definitions/Address.yaml' + tertiaryPickupAddress: + description: A third pickup address for this shipment, if the customer entered one. An optional field. + allOf: + - $ref: 'definitions/Address.yaml' + tertiaryDeliveryAddress: + description: A third delivery address for this shipment, if the customer entered one. An optional field. + allOf: + - $ref: 'definitions/Address.yaml' storageFacility: allOf: - x-nullable: true diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 199a7e00406..15f8eb5d167 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -406,6 +406,42 @@ paths: Returns counseling evaluation reports for the specified move that are visible to the current office user operationId: getMoveCounselingEvaluationReportsList + /moves/{moveID}/cancel: + parameters: + - description: ID of the move + in: path + name: moveID + required: true + format: uuid + type: string + post: + consumes: + - application/json + produces: + - application/json + parameters: [] + responses: + '200': + description: Successfully cancelled move + schema: + $ref: '#/definitions/Move' + '403': + $ref: '#/responses/PermissionDenied' + '404': + $ref: '#/responses/NotFound' + '409': + $ref: '#/responses/Conflict' + '412': + $ref: '#/responses/PreconditionFailed' + '422': + $ref: '#/responses/UnprocessableEntity' + '500': + $ref: '#/responses/ServerError' + tags: + - move + description: cancels a move + operationId: moveCanceler + summary: Cancels a move /counseling/orders/{orderID}: parameters: - description: ID of order to update @@ -3218,6 +3254,37 @@ paths: summary: Updates status of a payment request by id x-permissions: - update.paymentRequest + /payment-requests/{paymentRequestID}/bulkDownload: + parameters: + - description: the id for the payment-request with files to be downloaded + in: path + name: paymentRequestID + required: true + type: string + get: + summary: Downloads all Payment Request documents as a PDF + description: > + This endpoint downloads all uploaded payment request documentation + combined into a single PDF. + operationId: bulkDownload + tags: + - paymentRequests + produces: + - application/pdf + responses: + '200': + headers: + Content-Disposition: + type: string + description: File name to download + description: Payment Request Files PDF + schema: + format: binary + type: file + '400': + $ref: '#/responses/InvalidRequest' + '500': + $ref: '#/responses/ServerError' /documents/{documentId}: get: summary: Returns a document @@ -3321,6 +3388,7 @@ paths: - closeoutInitiated - closeoutLocation - ppmStatus + - counselingOffice - assignedTo description: field that results should be sorted by - in: query @@ -3342,6 +3410,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 @@ -3938,8 +4010,12 @@ paths: get: produces: - application/json - summary: Returns the transportation offices matching the search query - description: Returns the transportation offices matching the search query + summary: >- + Returns the transportation offices matching the search query that is + enabled for PPM closeout + description: >- + Returns the transportation offices matching the search query that is + enabled for PPM closeout operationId: getTransportationOffices tags: - transportationOffice @@ -4602,7 +4678,7 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 - dodID: + edipi: type: string userID: type: string @@ -4741,8 +4817,9 @@ definitions: $ref: '#/definitions/Affiliation' edipi: type: string - example: John - x-nullable: true + example: '1234567890' + maxLength: 10 + x-nullable: false emplid: type: string example: '9485155' @@ -6996,6 +7073,9 @@ definitions: ppmStatus: $ref: '#/definitions/PPMStatus' x-nullable: true + counselingOffice: + type: string + x-nullable: true assignedTo: $ref: '#/definitions/AssignedOfficeUser' x-nullable: true @@ -8590,6 +8670,7 @@ definitions: - NEEDS_ADVANCE_APPROVAL - NEEDS_CLOSEOUT - CLOSEOUT_COMPLETE + - CANCELED PPMAdvanceStatus: type: string title: PPM Advance Status @@ -10345,6 +10426,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: diff --git a/swagger/internal.yaml b/swagger/internal.yaml index 2ddb5861003..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 @@ -2866,6 +2866,7 @@ definitions: - NEEDS_ADVANCE_APPROVAL - NEEDS_CLOSEOUT - CLOSEOUT_COMPLETE + - CANCELED PPMAdvanceStatus: type: string title: PPM Advance Status diff --git a/swagger/prime.yaml b/swagger/prime.yaml index 56aafab5074..6da8e27e845 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -3126,6 +3126,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: @@ -3615,6 +3616,7 @@ definitions: - NEEDS_ADVANCE_APPROVAL - NEEDS_CLOSEOUT - CLOSEOUT_COMPLETE + - CANCELED SITLocationType: description: The list of SIT location types. type: string diff --git a/swagger/prime_v2.yaml b/swagger/prime_v2.yaml index bbcf7467de3..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: @@ -2200,6 +2201,7 @@ definitions: - NEEDS_ADVANCE_APPROVAL - NEEDS_CLOSEOUT - CLOSEOUT_COMPLETE + - CANCELED SITLocationType: description: The list of SIT location types. type: string diff --git a/swagger/prime_v3.yaml b/swagger/prime_v3.yaml index 699bb22d2b3..d13c94b496f 100644 --- a/swagger/prime_v3.yaml +++ b/swagger/prime_v3.yaml @@ -864,6 +864,16 @@ definitions: goods exist. allOf: - $ref: '#/definitions/Address' + hasTertiaryPickupAddress: + type: boolean + x-omitempty: false + x-nullable: true + tertiaryPickupAddress: + description: > + An optional third pickup location near the origin where additional + goods exist. + allOf: + - $ref: '#/definitions/Address' destinationAddress: description: > The address of the destination location where goods are being @@ -880,6 +890,16 @@ definitions: dropped off. allOf: - $ref: '#/definitions/Address' + hasTertiaryDestinationAddress: + type: boolean + x-omitempty: false + x-nullable: true + tertiaryDestinationAddress: + description: > + An optional third address near the destination where goods will be + dropped off. + allOf: + - $ref: '#/definitions/Address' sitExpected: description: > Captures whether some or all of the PPM shipment will require @@ -1047,6 +1067,18 @@ definitions: one. An optional field. allOf: - $ref: '#/definitions/Address' + tertiaryPickupAddress: + description: >- + A third pickup address for this shipment, if the customer entered one. + An optional field. + allOf: + - $ref: '#/definitions/Address' + tertiaryDeliveryAddress: + description: >- + A third delivery address for this shipment, if the customer entered + one. An optional field. + allOf: + - $ref: '#/definitions/Address' storageFacility: allOf: - x-nullable: true @@ -1735,6 +1767,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: @@ -2224,6 +2257,7 @@ definitions: - NEEDS_ADVANCE_APPROVAL - NEEDS_CLOSEOUT - CLOSEOUT_COMPLETE + - CANCELED SITLocationType: description: The list of SIT location types. type: string @@ -2303,6 +2337,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 @@ -2322,6 +2362,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 @@ -2725,17 +2771,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 From 5975c1be82d387f7048f3ee6283dbd5fa57bffa3 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Tue, 24 Sep 2024 19:14:19 +0000 Subject: [PATCH 17/28] updated with fix for test case --- pkg/services/order.go | 2 +- pkg/services/order/order_fetcher_test.go | 59 ++++++++++++------------ 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/pkg/services/order.go b/pkg/services/order.go index c3e1730132e..5ecac664a7a 100644 --- a/pkg/services/order.go +++ b/pkg/services/order.go @@ -69,5 +69,5 @@ type ListOrderParams struct { PPMStatus *string ViewAsGBLOC *string CounselingOffice *string - SCAssignedUser *string + SCAssignedUser *string } diff --git a/pkg/services/order/order_fetcher_test.go b/pkg/services/order/order_fetcher_test.go index 4b72919bb8f..20b9fa04166 100644 --- a/pkg/services/order/order_fetcher_test.go +++ b/pkg/services/order/order_fetcher_test.go @@ -158,8 +158,6 @@ func (suite *OrderServiceSuite) TestListOrders() { }) - - suite.Run("only returns visible moves (where show = True)", func() { // Under test: ListOrders // Set up: Make 2 moves, one correctly setup in setupTestData (show = True) @@ -580,39 +578,40 @@ func (suite *OrderServiceSuite) TestListOrders() { suite.Equal(createdPPM.Shipment.MoveTaskOrder.Locator, moves[0].Locator) }) } -func (suite *OrderServiceSuite) TestListOrderWithAssignedUserSingle(){ +func (suite *OrderServiceSuite) TestListOrderWithAssignedUserSingle() { // Under test: ListOrders - // Set up: Make a move, assign one to an SC office user - // Expected outcome: Only the one move with the assigned user should be returned - assignedOfficeUserUpdater := moveservice.NewAssignedOfficeUserUpdater(moveservice.NewMoveFetcher()) - scUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeTOO}) - var orderFetcherTest orderFetcher - session := auth.Session{ - ApplicationName: auth.OfficeApp, - Roles: scUser.User.Roles, - OfficeUserID: scUser.ID, - IDToken: "fake_token", - AccessToken: "fakeAccessToken", - } - - appCtx := suite.AppContextWithSessionForTest(&session) + // Set up: Make a move, assign one to an SC office user + // Expected outcome: Only the one move with the assigned user should be returned + assignedOfficeUserUpdater := moveservice.NewAssignedOfficeUserUpdater(moveservice.NewMoveFetcher()) + scUser := factory.BuildOfficeUserWithRoles(suite.DB(), nil, []roles.RoleType{roles.RoleTypeServicesCounselor}) + var orderFetcherTest orderFetcher + session := auth.Session{ + ApplicationName: auth.OfficeApp, + Roles: scUser.User.Roles, + OfficeUserID: scUser.ID, + IDToken: "fake_token", + AccessToken: "fakeAccessToken", + } + appCtx := suite.AppContextWithSessionForTest(&session) - createdMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) - expectedMove := createdMove - expectedMove.SCAssignedID = scUser.UserID - expectedMove.SCAssignedUser = &scUser - assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx,createdMove.ID,&scUser,roles.RoleTypeServicesCounselor) + createdMove := factory.BuildMoveWithShipment(suite.DB(), nil, nil) + createdMove.SCAssignedID = &scUser.ID + createdMove.SCAssignedUser = &scUser + _, updateError := assignedOfficeUserUpdater.UpdateAssignedOfficeUser(appCtx, createdMove.ID, &scUser, roles.RoleTypeServicesCounselor) - searchString := fmt.Sprintf("%s, %s",scUser.LastName,scUser.FirstName ) - moves, _, err := orderFetcherTest.ListOrders(suite.AppContextWithSessionForTest(&session), scUser.ID, &services.ListOrderParams{ - SCAssignedUser: &searchString, - }) + searchString := fmt.Sprintf("%s, %s", scUser.LastName, scUser.FirstName) + moves, _, err := orderFetcherTest.ListOrders(suite.AppContextWithSessionForTest(&session), scUser.ID, &services.ListOrderParams{ + SCAssignedUser: &searchString, + }) - suite.FatalNoError(err) - suite.Equal(1, len(moves)) - suite.Equal(moves[0].SCAssignedID, expectedMove.SCAssignedID) - suite.Equal(moves[0].SCAssignedUser, expectedMove.SCAssignedUser) + suite.FatalNoError(err) + suite.FatalNoError(updateError) + suite.Equal(1, len(moves)) + suite.Equal(moves[0].SCAssignedID, createdMove.SCAssignedID) + suite.Equal(createdMove.SCAssignedUser.ID, moves[0].SCAssignedUser.ID) + suite.Equal(createdMove.SCAssignedUser.FirstName, moves[0].SCAssignedUser.FirstName) + suite.Equal(createdMove.SCAssignedUser.LastName, moves[0].SCAssignedUser.LastName) } func (suite *OrderServiceSuite) TestListOrdersUSMCGBLOC() { orderFetcher := NewOrderFetcher() From 00088cb6d81f4fb9c3d6988771b571aa14665876 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Wed, 25 Sep 2024 13:30:29 +0000 Subject: [PATCH 18/28] removed duplicate counseling office --- swagger-def/ghc.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 422c7056d4a..46a0079fc7b 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3269,7 +3269,6 @@ paths: ppmStatus, counselingOffice, assignedTo, - counselingOffice, ] description: field that results should be sorted by - in: query From d39c2cd28461daeedfba7632a7e7866d2aaf2017 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Wed, 25 Sep 2024 15:32:38 +0000 Subject: [PATCH 19/28] removed commented out code --- pkg/factory/move_factory.go | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/pkg/factory/move_factory.go b/pkg/factory/move_factory.go index 04d24b49600..62de65a23d2 100644 --- a/pkg/factory/move_factory.go +++ b/pkg/factory/move_factory.go @@ -34,11 +34,6 @@ func BuildMove(db *pop.Connection, customs []Customization, traits []Trait) mode tempCloseoutOfficeCustoms = convertCustomizationInList(tempCloseoutOfficeCustoms, TransportationOffices.CloseoutOffice, TransportationOffice) closeoutOffice = BuildTransportationOffice(db, tempCloseoutOfficeCustoms, nil) } - /* var assignedUser models.OfficeUser - assignedUserResult := findValidCustomization(customs, OfficeUser) - if assignedUserResult != nil { - assignedUser = BuildOfficeUserWithRoles(db,customs ,nil) - } */ var defaultReferenceID string var err error @@ -71,18 +66,6 @@ func BuildMove(db *pop.Connection, customs []Customization, traits []Trait) mode ContractorID: &contractor.ID, ReferenceID: &defaultReferenceID, } - /* if(assignedUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor)){ - move.SCAssignedUser = &assignedUser - move.SCAssignedID = assignedUser.UserID - } - if(assignedUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor)){ - move.TIOAssignedUser = &assignedUser - move.TIOAssignedID = assignedUser.UserID - } - if(assignedUser.User.Roles.HasRole(roles.RoleTypeServicesCounselor)){ - move.TOOAssignedUser = &assignedUser - move.TOOAssignedID = assignedUser.UserID - } */ if closeoutOfficeResult != nil { move.CloseoutOffice = &closeoutOffice From ef2f5856e5de508593c39d9cffc6a53cc21ae888 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Thu, 26 Sep 2024 19:30:53 +0000 Subject: [PATCH 20/28] added missing Assigned payload variable. flaky test, post merge fixes --- .../internal/payloads/model_to_payload.go | 1 + src/components/Table/TableQueue.jsx | 2 + .../ServicesCounselingQueue.jsx | 296 +++++++++--------- .../ServicesCounselingQueue.test.jsx | 12 +- 4 files changed, 160 insertions(+), 151 deletions(-) diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 933d9431941..1197903ba0d 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -2115,6 +2115,7 @@ func QueueMoves(moves []models.Move) *ghcmessages.QueueMoves { LockExpiresAt: handlers.FmtDateTimePtr(move.LockExpiresAt), PpmStatus: ghcmessages.PPMStatus(ppmStatus), CounselingOffice: &transportationOffice, + AssignedTo: AssignedOfficeUser(move.SCAssignedUser), } } return &queueMoves diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index b00e60b0140..9cf3988a684 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -49,6 +49,8 @@ const TableQueue = ({ csvExportQueueFetcher, csvExportQueueFetcherKey, sessionStorageKey, + isSupervisor, + currentUserId, isHeadquartersUser, }) => { const [isPageReload, setIsPageReload] = useState(true); diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 68940e9cb55..5c85a2e72e7 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -50,168 +50,174 @@ import CustomerSearchForm from 'components/CustomerSearchForm/CustomerSearchForm import MultiSelectTypeAheadCheckBoxFilter from 'components/Table/Filters/MutliSelectTypeAheadCheckboxFilter'; import { formatAvailableOfficeUsersForRow, handleQueueAssignment } from 'utils/queues'; -export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => [ - createHeader( - ' ', - (row) => { - const now = new Date(); - // this will render a lock icon if the move is locked & if the lockExpiresAt value is after right now - if (row.lockedByOfficeUserID && row.lockExpiresAt && now < new Date(row.lockExpiresAt) && moveLockFlag) { +export const counselingColumns = (moveLockFlag, originLocationList, supervisor, isQueueManagementEnabled) => { + const cols = [ + createHeader( + ' ', + (row) => { + const now = new Date(); + // this will render a lock icon if the move is locked & if the lockExpiresAt value is after right now + if (row.lockedByOfficeUserID && row.lockExpiresAt && now < new Date(row.lockExpiresAt) && moveLockFlag) { + return ( +
+ +
+ ); + } + return null; + }, + { id: 'lock' }, + ), + createHeader('ID', 'id', { id: 'id' }), + createHeader( + 'Customer name', + (row) => { return ( -
- +
+ {CHECK_SPECIAL_ORDERS_TYPES(row.orderType) ? ( + {SPECIAL_ORDERS_TYPES[`${row.orderType}`]} + ) : null} + {`${row.customer.last_name}, ${row.customer.first_name}`}
); - } - return null; - }, - { id: 'lock' }, - ), - createHeader('ID', 'id', { id: 'id' }), - createHeader( - 'Customer name', - (row) => { - return ( -
- {CHECK_SPECIAL_ORDERS_TYPES(row.orderType) ? ( - {SPECIAL_ORDERS_TYPES[`${row.orderType}`]} - ) : null} - {`${row.customer.last_name}, ${row.customer.first_name}`} -
- ); - }, - { - id: 'lastName', + }, + { + id: 'lastName', + isFilterable: true, + exportValue: (row) => { + return `${row.customer.last_name}, ${row.customer.first_name}`; + }, + }, + ), + createHeader('DoD ID', 'customer.dodID', { + id: 'dodID', isFilterable: true, exportValue: (row) => { - return `${row.customer.last_name}, ${row.customer.first_name}`; + return row.customer.dodID; }, - }, - ), - createHeader('DoD ID', 'customer.dodID', { - id: 'dodID', - isFilterable: true, - exportValue: (row) => { - return row.customer.dodID; - }, - }), - createHeader('EMPLID', 'customer.emplid', { - id: 'emplid', - isFilterable: true, - }), - createHeader('Move code', 'locator', { - id: 'locator', - isFilterable: true, - }), - createHeader( - 'Status', - (row) => { - return row.status !== MOVE_STATUSES.SERVICE_COUNSELING_COMPLETED - ? SERVICE_COUNSELING_MOVE_STATUS_LABELS[`${row.status}`] - : null; - }, - { - id: 'status', - disableSortBy: true, - }, - ), - createHeader( - 'Requested move date', - (row) => { - return formatDateFromIso(row.requestedMoveDate, DATE_FORMAT_STRING); - }, - { - id: 'requestedMoveDate', - isFilterable: true, - // eslint-disable-next-line react/jsx-props-no-spreading - Filter: (props) => , - }, - ), - createHeader( - 'Date submitted', - (row) => { - return formatDateFromIso(row.submittedAt, DATE_FORMAT_STRING); - }, - { - id: 'submittedAt', + }), + createHeader('EMPLID', 'customer.emplid', { + id: 'emplid', isFilterable: true, - // eslint-disable-next-line react/jsx-props-no-spreading - Filter: (props) => , - }, - ), - createHeader( - 'Branch', - (row) => { - return serviceMemberAgencyLabel(row.customer.agency); - }, - { - id: 'branch', + }), + createHeader('Move code', 'locator', { + id: 'locator', isFilterable: true, - Filter: (props) => ( + }), + createHeader( + 'Status', + (row) => { + return row.status !== MOVE_STATUSES.SERVICE_COUNSELING_COMPLETED + ? SERVICE_COUNSELING_MOVE_STATUS_LABELS[`${row.status}`] + : null; + }, + { + id: 'status', + disableSortBy: true, + }, + ), + createHeader( + 'Requested move date', + (row) => { + return formatDateFromIso(row.requestedMoveDate, DATE_FORMAT_STRING); + }, + { + id: 'requestedMoveDate', + isFilterable: true, // eslint-disable-next-line react/jsx-props-no-spreading - - ), - }, - ), - createHeader('Origin GBLOC', 'originGBLOC', { - disableSortBy: true, - }), // If the user is in the USMC GBLOC they will have many different GBLOCs and will want to sort and filter - supervisor - ? createHeader( - 'Origin duty location', - (row) => { - return `${row.originDutyLocation.name}`; - }, - { - id: 'originDutyLocation', - isFilterable: true, - exportValue: (row) => { - return row.originDutyLocation?.name; - }, - Filter: (props) => ( - - ), - }, - ) - : createHeader('Origin duty location', 'originDutyLocation.name', { - id: 'originDutyLocation', + Filter: (props) => , + }, + ), + createHeader( + 'Date submitted', + (row) => { + return formatDateFromIso(row.submittedAt, DATE_FORMAT_STRING); + }, + { + id: 'submittedAt', isFilterable: true, - exportValue: (row) => { - return row.originDutyLocation?.name; - }, - }), - createHeader('Counseling office', 'counselingOffice', { - id: 'counselingOffice', - isFilterable: true, - }), - isQueueManagementEnabled ?? + // eslint-disable-next-line react/jsx-props-no-spreading + Filter: (props) => , + }, + ), createHeader( - 'Assigned', + 'Branch', (row) => { - const { formattedAvailableOfficeUsers, assignedToUser } = formatAvailableOfficeUsersForRow(row); - return ( -
- handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR)} - title="Assigned dropdown" - > - {formattedAvailableOfficeUsers} - -
- ); + return serviceMemberAgencyLabel(row.customer.agency); }, { - id: 'assignedTo', + id: 'branch', isFilterable: true, + Filter: (props) => ( + // eslint-disable-next-line react/jsx-props-no-spreading + + ), }, ), -]; + createHeader('Origin GBLOC', 'originGBLOC', { + disableSortBy: true, + }), // If the user is in the USMC GBLOC they will have many different GBLOCs and will want to sort and filter + supervisor + ? createHeader( + 'Origin duty location', + (row) => { + return `${row.originDutyLocation.name}`; + }, + { + id: 'originDutyLocation', + isFilterable: true, + exportValue: (row) => { + return row.originDutyLocation?.name; + }, + Filter: (props) => ( + + ), + }, + ) + : createHeader('Origin duty location', 'originDutyLocation.name', { + id: 'originDutyLocation', + isFilterable: true, + exportValue: (row) => { + return row.originDutyLocation?.name; + }, + }), + createHeader('Counseling office', 'counselingOffice', { + id: 'counselingOffice', + isFilterable: true, + }), + ]; + if (isQueueManagementEnabled) + cols.push( + createHeader( + 'Assigned', + (row) => { + const { formattedAvailableOfficeUsers, assignedToUser } = formatAvailableOfficeUsersForRow(row); + return ( +
+ handleQueueAssignment(row.id, e.target.value, roleTypes.SERVICES_COUNSELOR)} + title="Assigned dropdown" + > + {formattedAvailableOfficeUsers} + +
+ ); + }, + { + id: 'assignedTo', + isFilterable: true, + }, + ), + ); + + return cols; +}; 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 d79b4b92247..ebaa1fcc242 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx @@ -276,8 +276,8 @@ describe('ServicesCounselingQueue', () => { expect(firstMove.find('td.dodID').text()).toBe('555555555'); expect(firstMove.find('td.locator').text()).toBe('AB5PC'); expect(firstMove.find('td.status').text()).toBe('Needs counseling'); - expect(firstMove.find('td.requestedMoveDate').text()).toBe('28 Feb 2021'); - expect(firstMove.find('td.submittedAt').text()).toBe('30 Jan 2021'); + expect(firstMove.find('td.requestedMoveDate').text()).toBe('01 Mar 2021'); + expect(firstMove.find('td.submittedAt').text()).toBe('31 Jan 2021'); expect(firstMove.find('td.branch').text()).toBe('Army'); expect(firstMove.find('td.originGBLOC').text()).toBe('LKNQ'); expect(firstMove.find('td.originDutyLocation').text()).toBe('Area 51'); @@ -289,8 +289,8 @@ describe('ServicesCounselingQueue', () => { expect(secondMove.find('td.emplid').text()).toBe('4521567'); expect(secondMove.find('td.locator').text()).toBe('T12AR'); expect(secondMove.find('td.status').text()).toBe('Needs counseling'); - expect(secondMove.find('td.requestedMoveDate').text()).toBe('14 Apr 2021'); - expect(secondMove.find('td.submittedAt').text()).toBe('31 Dec 2020'); + expect(secondMove.find('td.requestedMoveDate').text()).toBe('15 Apr 2021'); + expect(secondMove.find('td.submittedAt').text()).toBe('01 Jan 2021'); expect(secondMove.find('td.branch').text()).toBe('Coast Guard'); expect(secondMove.find('td.originGBLOC').text()).toBe('LKNQ'); expect(secondMove.find('td.originDutyLocation').text()).toBe('Los Alamos'); @@ -301,8 +301,8 @@ describe('ServicesCounselingQueue', () => { expect(thirdMove.find('td.dodID').text()).toBe('4444444444'); expect(thirdMove.find('td.locator').text()).toBe('T12MP'); expect(thirdMove.find('td.status').text()).toBe('Needs counseling'); - expect(thirdMove.find('td.requestedMoveDate').text()).toBe('14 Apr 2021'); - expect(thirdMove.find('td.submittedAt').text()).toBe('31 Dec 2020'); + expect(thirdMove.find('td.requestedMoveDate').text()).toBe('15 Apr 2021'); + expect(thirdMove.find('td.submittedAt').text()).toBe('01 Jan 2021'); expect(thirdMove.find('td.branch').text()).toBe('Marine Corps'); expect(thirdMove.find('td.originGBLOC').text()).toBe('LKNQ'); expect(thirdMove.find('td.originDutyLocation').text()).toBe('Denver, 80136'); From 7eeb97c122726bb35b0248ab91352f43b31057a8 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Tue, 1 Oct 2024 02:15:18 +0000 Subject: [PATCH 21/28] Removed Space in move object, consolidated queue util method, added tests for queue util --- pkg/services/move.go | 1 + src/components/Table/TableQueue.jsx | 8 ++--- src/utils/queues.jsx | 16 ++++------ src/utils/queues.test.jsx | 45 +++++++++++++++++++++++++++++ 4 files changed, 56 insertions(+), 14 deletions(-) create mode 100644 src/utils/queues.test.jsx diff --git a/pkg/services/move.go b/pkg/services/move.go index 99b9f3a775f..a641d16c76c 100644 --- a/pkg/services/move.go +++ b/pkg/services/move.go @@ -119,6 +119,7 @@ type MoveCloseoutOfficeUpdater interface { type MoveCanceler interface { CancelMove(appCtx appcontext.AppContext, moveID uuid.UUID) (*models.Move, error) } + type MoveAssignedOfficeUserUpdater interface { UpdateAssignedOfficeUser(appCtx appcontext.AppContext, moveID uuid.UUID, officeUser *models.OfficeUser, role roles.RoleType) (*models.Move, error) DeleteAssignedOfficeUser(appCtx appcontext.AppContext, moveID uuid.UUID, role roles.RoleType) (*models.Move, error) diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 9cf3988a684..39868b94a09 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -125,10 +125,10 @@ const TableQueue = ({ const tableData = useMemo(() => data, [data]); const formattedAvailableOfficeUsers = formatAvailableOfficeUsers(availableOfficeUsers, isSupervisor, currentUserId); // attach the available office users to the moves/row - const tableDataWithAvailableUsers = tableData?.map((ele) => { - const newEle = { ...ele }; - newEle.availableOfficeUsers = formattedAvailableOfficeUsers; - return newEle; + const tableDataWithAvailableUsers = tableData?.map((row) => { + const CurrentRow = { ...row }; + CurrentRow.availableOfficeUsers = formattedAvailableOfficeUsers; + return row; }); const tableColumns = useMemo(() => columns, [columns]); diff --git a/src/utils/queues.jsx b/src/utils/queues.jsx index 8eccb95d25a..5454232f214 100644 --- a/src/utils/queues.jsx +++ b/src/utils/queues.jsx @@ -3,21 +3,17 @@ import React from 'react'; import { deleteAssignedOfficeUserForMove, updateAssignedOfficeUserForMove } from 'services/ghcApi'; import { DEFAULT_EMPTY_VALUE } from 'shared/constants'; -const addAssignedOfficeUser = (users, assignedTo) => { - const newAvailableOfficeUsers = users.slice(); - const { lastname, firstname, id } = assignedTo; - newAvailableOfficeUsers.push({ - label: `${lastname}, ${firstname}`, - value: id, - }); - return newAvailableOfficeUsers; -}; - export const formatOfficeUser = (user) => { const fullName = `${user?.lastName}, ${user?.firstName}`; return { label: fullName, value: user.officeUserId }; }; +const addAssignedOfficeUser = (users, assignedTo) => { + const newAvailableOfficeUsers = users.slice(); + newAvailableOfficeUsers.push(formatOfficeUser(assignedTo)); + return newAvailableOfficeUsers; +}; + export const formatAvailableOfficeUsers = (users, isSupervisor, currentUserId) => { if (!users.length || isSupervisor === undefined || currentUserId === undefined) return []; diff --git a/src/utils/queues.test.jsx b/src/utils/queues.test.jsx new file mode 100644 index 00000000000..c66f5e6cbd6 --- /dev/null +++ b/src/utils/queues.test.jsx @@ -0,0 +1,45 @@ +import { formatOfficeUser, formatAvailableOfficeUsers } from './queues'; + +const users = [ + { + firstName: 'John', + lastName: 'Doe', + officeUserId: '1234', + }, + { + firstName: 'Lorem', + lastName: 'Ipsum', + officeUserId: '5678', + }, + { + firstName: 'Current', + lastName: 'User', + officeUserId: '123456', + }, +]; + +describe('formatOfficeUser', () => { + it('should format a single office user', () => { + const formattedUser = formatOfficeUser(users[0]); + expect(formattedUser.label).toBe('Doe, John'); + expect(formattedUser.value).toBe('1234'); + }); + it('should format the office users dropdown where current user is not a supervisor', () => { + const formattedAvailableUsers = formatAvailableOfficeUsers(users, false, '123456'); + expect(formattedAvailableUsers[0].label).toBe('—'); + expect(formattedAvailableUsers[0].value).toBe(null); + expect(formattedAvailableUsers[1].label).toBe('User, Current'); + expect(formattedAvailableUsers[1].value).toBe('123456'); + }); + it('should format the office users dropdown where current user is a supervisor', () => { + const formattedAvailableUsers = formatAvailableOfficeUsers(users, true, '123456'); + expect(formattedAvailableUsers[0].label).toBe('—'); + expect(formattedAvailableUsers[0].value).toBe(null); + expect(formattedAvailableUsers[1].label).toBe('Doe, John'); + expect(formattedAvailableUsers[1].value).toBe('1234'); + expect(formattedAvailableUsers[2].label).toBe('Ipsum, Lorem'); + expect(formattedAvailableUsers[2].value).toBe('5678'); + expect(formattedAvailableUsers[3].label).toBe('User, Current'); + expect(formattedAvailableUsers[3].value).toBe('123456'); + }); +}); From 0510be55aaff145c5778c613d8805c0465ca69f8 Mon Sep 17 00:00:00 2001 From: joeydoyecaci Date: Tue, 1 Oct 2024 15:16:46 +0000 Subject: [PATCH 22/28] Added additional test cases for queues.test, added eslint req'd changes --- src/utils/queues.jsx | 7 +- src/utils/queues.test.jsx | 132 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 132 insertions(+), 7 deletions(-) diff --git a/src/utils/queues.jsx b/src/utils/queues.jsx index 5454232f214..3f55ec79255 100644 --- a/src/utils/queues.jsx +++ b/src/utils/queues.jsx @@ -43,13 +43,16 @@ export const formatAvailableOfficeUsersForRow = (row) => { // if the move is assigned to a user not present in availableOfficeUsers // lets push them onto the end - if (row.assignedTo !== undefined && !row.availableOfficeUsers?.some((user) => user.value === row.assignedTo.id)) { + if ( + row.assignedTo !== undefined && + !row.availableOfficeUsers?.some((user) => user.value === row.assignedTo.officeUserId) + ) { updatedRow.availableOfficeUsers = addAssignedOfficeUser(row.availableOfficeUsers, row.assignedTo); } const { assignedTo, availableOfficeUsers } = updatedRow; // if there is an assigned user, assign to a variable so we can set a default value below - const assignedToUser = availableOfficeUsers.find((user) => user.value === assignedTo?.id); + const assignedToUser = availableOfficeUsers.find((user) => user.value === assignedTo?.officeUserId); const formattedAvailableOfficeUsers = availableOfficeUsers.map(({ value, label }) => (
);