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/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index 14b898bc6e1..1578327c115 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -994,3 +994,6 @@ 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 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/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/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 8184c5d5aed..4e4077fd3ae 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -4045,7 +4045,8 @@ func init() { "ppmType", "closeoutInitiated", "closeoutLocation", - "ppmStatus" + "ppmStatus", + "counselingOffice" ], "type": "string", "description": "field that results should be sorted by", @@ -4080,6 +4081,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", @@ -7269,10 +7276,10 @@ func init() { "current_address": { "$ref": "#/definitions/Address" }, - "dodID": { + "eTag": { "type": "string" }, - "eTag": { + "edipi": { "type": "string" }, "email": { @@ -11567,6 +11574,10 @@ func init() { "type": "string", "x-nullable": true }, + "counselingOffice": { + "type": "string", + "x-nullable": true + }, "customer": { "$ref": "#/definitions/Customer" }, @@ -12349,7 +12360,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -19147,7 +19159,8 @@ func init() { "ppmType", "closeoutInitiated", "closeoutLocation", - "ppmStatus" + "ppmStatus", + "counselingOffice" ], "type": "string", "description": "field that results should be sorted by", @@ -19182,6 +19195,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", @@ -22757,10 +22776,10 @@ func init() { "current_address": { "$ref": "#/definitions/Address" }, - "dodID": { + "eTag": { "type": "string" }, - "eTag": { + "edipi": { "type": "string" }, "email": { @@ -27130,6 +27149,10 @@ func init() { "type": "string", "x-nullable": true }, + "counselingOffice": { + "type": "string", + "x-nullable": true + }, "customer": { "$ref": "#/definitions/Customer" }, @@ -27962,7 +27985,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { 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..6d1745d74fd 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 @@ -46,6 +46,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 */ @@ -154,6 +158,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) @@ -327,6 +336,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 @@ -696,7 +723,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", "counselingOffice"}, 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 a53c0b78532..c9549f94ac2 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 @@ -19,6 +19,7 @@ type GetServicesCounselingQueueURL struct { Branch *string CloseoutInitiated *strfmt.DateTime CloseoutLocation *string + CounselingOffice *string DestinationDutyLocation *string DodID *string Emplid *string @@ -97,6 +98,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/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/queue_move.go b/pkg/gen/ghcmessages/queue_move.go index 31b88d42f81..47f8611502e 100644 --- a/pkg/gen/ghcmessages/queue_move.go +++ b/pkg/gen/ghcmessages/queue_move.go @@ -31,6 +31,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 2c2fae1b7fd..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" }, @@ -6430,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" }, @@ -6906,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" }, @@ -12589,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" }, @@ -15036,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" }, @@ -15514,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/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 3f7c03e0d5c..07968eff65b 100644 --- a/pkg/gen/primeapi/embedded_spec.go +++ b/pkg/gen/primeapi/embedded_spec.go @@ -3599,7 +3599,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -8293,7 +8294,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { 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 c42bc689674..c06735be87b 100644 --- a/pkg/gen/primev2api/embedded_spec.go +++ b/pkg/gen/primev2api/embedded_spec.go @@ -2587,7 +2587,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -5926,7 +5927,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { 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 8dfca4e3c05..15de952a6ae 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", @@ -2635,7 +2647,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { @@ -5084,20 +5097,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" @@ -5128,6 +5131,12 @@ func init() { } ] }, + "tertiaryDeliveryAddress": { + "$ref": "#/definitions/Address" + }, + "tertiaryPickupAddress": { + "$ref": "#/definitions/Address" + }, "updatedAt": { "type": "string", "format": "date-time", @@ -5475,6 +5484,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", @@ -5572,6 +5591,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", @@ -6066,7 +6091,8 @@ func init() { "ZipSITOriginHHGOriginalAddress", "StandaloneCrate", "StandaloneCrateCap", - "UncappedRequestTotal" + "UncappedRequestTotal", + "LockedPriceCents" ] }, "ServiceItemParamOrigin": { 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/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/handlers/ghcapi/customer_test.go b/pkg/handlers/ghcapi/customer_test.go index ca46f85b2a5..4e8564af27c 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) diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index f024da83510..d34bde73765 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -490,7 +490,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()), @@ -2024,6 +2024,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 @@ -2095,6 +2099,7 @@ func QueueMoves(moves []models.Move) *ghcmessages.QueueMoves { LockedByOfficeUser: OfficeUser(move.LockedByOfficeUser), LockExpiresAt: handlers.FmtDateTimePtr(move.LockExpiresAt), PpmStatus: ghcmessages.PPMStatus(ppmStatus), + CounselingOffice: &transportationOffice, } } return &queueMoves 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 1781ff91238..a4231d9df06 100644 --- a/pkg/handlers/ghcapi/orders.go +++ b/pkg/handlers/ghcapi/orders.go @@ -186,13 +186,6 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. return orderop.NewCreateOrderUnprocessableEntity(), err } - 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 - } - if payload.Sac != nil && len(*payload.Sac) > SAC_LIMIT { err = apperror.NewBadDataError("SAC cannot be more than 80 characters.") appCtx.Logger().Error(err.Error()) @@ -317,30 +310,27 @@ func (h CreateOrderHandler) Handle(params orderop.CreateOrderParams) middleware. } moveOptions := models.MoveOptions{ - Show: models.BoolPointer(true), - Status: &status, - CounselingOfficeID: &officeUser.TransportationOfficeID, + 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/queues.go b/pkg/handlers/ghcapi/queues.go index f994b55a4eb..8178ddb8515 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, } if params.NeedsPPMCloseout != nil && *params.NeedsPPMCloseout { 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/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/primeapi/mto_shipment_address.go b/pkg/handlers/primeapi/mto_shipment_address.go index 2f050cb947a..64e658d251c 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 == 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) { 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 == 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/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/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/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/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_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_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 9064ee79974..7a133fe0bae 100644 --- a/pkg/services/order.go +++ b/pkg/services/order.go @@ -68,4 +68,5 @@ type ListOrderParams struct { OrderType *string PPMStatus *string ViewAsGBLOC *string + CounselingOffice *string } diff --git a/pkg/services/order/order_fetcher.go b/pkg/services/order/order_fetcher.go index 0d018dac81e..d34488b9e3e 100644 --- a/pkg/services/order/order_fetcher.go +++ b/pkg/services/order/order_fetcher.go @@ -117,9 +117,9 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid ppmTypeQuery := ppmTypeFilter(params.PPMType) ppmStatusQuery := ppmStatusFilter(params.PPMStatus) 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 := [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, counselingQuery} var query *pop.Query if ppmCloseoutGblocs { query = appCtx.DB().Q().Scope(utilities.ExcludeDeletedScope(models.MTOShipment{})).EagerPreload( @@ -157,6 +157,7 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid "MTOShipments.PPMShipment", "CloseoutOffice", "LockedByOfficeUser", + "CounselingOffice", ).InnerJoin("orders", "orders.id = moves.orders_id"). InnerJoin("service_members", "orders.service_member_id = service_members.id"). InnerJoin("mto_shipments", "moves.id = mto_shipments.move_id"). @@ -168,6 +169,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"). Where("show = ?", models.BoolPointer(true)) if !privileges.HasPrivilege(models.PrivilegeTypeSafety) { @@ -230,6 +232,10 @@ func (f orderFetcher) ListOrders(appCtx appcontext.AppContext, officeUserID uuid groupByColumms = append(groupByColumms, "ppm_shipments.id") } + if params.Sort != nil && *params.Sort == "counselingOffice" { + groupByColumms = append(groupByColumms, "transportation_offices.id") + } + err = query.GroupBy("moves.id", groupByColumms...).Paginate(int(*params.Page), int(*params.PerPage)).All(&moves) if err != nil { return []models.Move{}, 0, err @@ -542,6 +548,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 @@ -687,6 +701,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", } return func(query *pop.Query) { 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/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/Office/DefinitionLists/CustomerInfoList.jsx b/src/components/Office/DefinitionLists/CustomerInfoList.jsx index b1b79d9ac01..16a698529f4 100644 --- a/src/components/Office/DefinitionLists/CustomerInfoList.jsx +++ b/src/components/Office/DefinitionLists/CustomerInfoList.jsx @@ -19,7 +19,7 @@ const CustomerInfoList = ({ customerInfo }) => {
DoD ID
-
{customerInfo.dodId}
+
{customerInfo.edipi}
{customerInfo.agency === departmentIndicators.COAST_GUARD && (
@@ -81,7 +81,7 @@ const CustomerInfoList = ({ customerInfo }) => { CustomerInfoList.propTypes = { customerInfo: PropTypes.shape({ name: PropTypes.string, - dodId: PropTypes.string, + edipi: PropTypes.string, phone: PropTypes.string, email: PropTypes.string, currentAddress: AddressShape, diff --git a/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx b/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx index 18682850c78..d957be52b4f 100644 --- a/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx +++ b/src/components/Office/DefinitionLists/CustomerInfoList.test.jsx @@ -6,7 +6,7 @@ import CustomerInfoList from './CustomerInfoList'; const info = { name: 'Smith, Kerry', agency: 'COAST_GUARD', - dodId: '9999999999', + edipi: '9999999999', emplid: '7777777', phone: '999-999-9999', altPhone: '888-888-8888', diff --git a/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx b/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx index 3dc4c9dadab..2d557e2c874 100644 --- a/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx +++ b/src/components/Office/ServicesCounselingTabNav/ServicesCounselingTabNav.jsx @@ -10,7 +10,7 @@ import 'styles/office.scss'; import TabNav from 'components/TabNav'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; -const ServicesCounselingTabNav = ({ unapprovedShipmentCount = 0, moveCode }) => { +const ServicesCounselingTabNav = ({ unapprovedShipmentCount = 0, missingOrdersInfoCount, moveCode }) => { const [supportingDocsFF, setSupportingDocsFF] = React.useState(false); React.useEffect(() => { const fetchData = async () => { @@ -19,6 +19,14 @@ const ServicesCounselingTabNav = ({ unapprovedShipmentCount = 0, moveCode }) => fetchData(); }, []); + let moveDetailsTagCount = 0; + if (unapprovedShipmentCount > 0) { + moveDetailsTagCount += unapprovedShipmentCount; + } + if (missingOrdersInfoCount > 0) { + moveDetailsTagCount += missingOrdersInfoCount; + } + const items = [ data-testid="MoveDetails-Tab" > Move details - {unapprovedShipmentCount > 0 && {unapprovedShipmentCount}} + {moveDetailsTagCount > 0 && {moveDetailsTagCount}} , { expect(within(moveDetailsTab).queryByTestId('tag')).not.toBeInTheDocument(); }); - it('should render the move details tab container with a tag that shows the count of unapproved shipments', () => { + it('should render the move details tab container with a tag that shows the count of action items', () => { const moveDetailsShipmentAndAmendedOrders = { ...basicNavProps, unapprovedShipmentCount: 6, + missingOrdersInfoCount: 4, }; render(, { wrapper: MemoryRouter }); const moveDetailsTab = screen.getByTestId('MoveDetails-Tab'); - expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('6'); + expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('10'); }); }); diff --git a/src/components/Office/TXOTabNav/TXOTabNav.jsx b/src/components/Office/TXOTabNav/TXOTabNav.jsx index 9732c46407e..d3db9fbd1b9 100644 --- a/src/components/Office/TXOTabNav/TXOTabNav.jsx +++ b/src/components/Office/TXOTabNav/TXOTabNav.jsx @@ -17,6 +17,7 @@ const TXOTabNav = ({ excessWeightRiskCount, pendingPaymentRequestCount, unapprovedSITExtensionCount, + missingOrdersInfoCount, shipmentsWithDeliveryAddressUpdateRequestedCount, order, moveCode, @@ -39,6 +40,9 @@ const TXOTabNav = ({ if (shipmentsWithDeliveryAddressUpdateRequestedCount) { moveDetailsTagCount += shipmentsWithDeliveryAddressUpdateRequestedCount; } + if (missingOrdersInfoCount > 0) { + moveDetailsTagCount += missingOrdersInfoCount; + } let moveTaskOrderTagCount = 0; if (unapprovedServiceItemCount > 0) { diff --git a/src/components/Office/TXOTabNav/TXOTabNav.test.jsx b/src/components/Office/TXOTabNav/TXOTabNav.test.jsx index 7298a9d4d08..2779c53b11e 100644 --- a/src/components/Office/TXOTabNav/TXOTabNav.test.jsx +++ b/src/components/Office/TXOTabNav/TXOTabNav.test.jsx @@ -41,11 +41,12 @@ describe('Move details tag rendering', () => { const moveDetailsOneShipment = { ...basicNavProps, unapprovedShipmentCount: 1, + missingOrdersInfoCount: 4, }; render(, { wrapper: MemoryRouter }); const moveDetailsTab = screen.getByTestId('MoveDetails-Tab'); - expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('1'); + expect(within(moveDetailsTab).getByTestId('tag')).toHaveTextContent('5'); }); it('should render the move details tab container with a tag that shows the count of items that need attention when there are approved shipments with a destination address update requiring TXO review', () => { diff --git a/src/components/PrimeUI/Shipment/Shipment.jsx b/src/components/PrimeUI/Shipment/Shipment.jsx index 1625cc91240..ba59e51a98e 100644 --- a/src/components/PrimeUI/Shipment/Shipment.jsx +++ b/src/components/PrimeUI/Shipment/Shipment.jsx @@ -190,11 +190,31 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => {
{formatPrimeAPIShipmentAddress(shipment.pickupAddress)}
{shipment.pickupAddress?.id && moveId && Edit}
+
+
Second Pickup Address:
+
{formatPrimeAPIShipmentAddress(shipment.secondaryPickupAddress)}
+
{shipment.secondaryPickupAddress?.id && moveId && Edit}
+
+
+
Third Pickup Address:
+
{formatPrimeAPIShipmentAddress(shipment.tertiaryPickupAddress)}
+
{shipment.tertiaryPickupAddress?.id && moveId && Edit}
+
Destination Address:
{formatPrimeAPIShipmentAddress(shipment.destinationAddress)}
{shipment.destinationAddress?.id && moveId && Edit}
+
+
Second Destination Address:
+
{formatPrimeAPIShipmentAddress(shipment.secondaryDeliveryAddress)}
+
{shipment.secondaryDeliveryAddress?.id && moveId && Edit}
+
+
+
Third Destination Address:
+
{formatPrimeAPIShipmentAddress(shipment.tertiaryDeliveryAddress)}
+
{shipment.tertiaryDeliveryAddress?.id && moveId && Edit}
+
Destination type:
@@ -271,17 +291,25 @@ const Shipment = ({ shipment, moveId, onDelete, mtoServiceItems }) => {
{formatPrimeAPIShipmentAddress(shipment.ppmShipment.pickupAddress)}
-
Secondary Pickup Address:
+
Second Pickup Address:
{formatPrimeAPIShipmentAddress(shipment.ppmShipment.secondaryPickupAddress)}
+
+
Third Pickup Address:
+
{formatPrimeAPIShipmentAddress(shipment.ppmShipment.tertiaryPickupAddress)}
+
Destination Address:
{formatPrimeAPIShipmentAddress(shipment.ppmShipment.destinationAddress)}
-
Secondary Destination Address:
+
Second Destination Address:
{formatPrimeAPIShipmentAddress(shipment.ppmShipment.secondaryDestinationAddress)}
+
+
Third Destination Address:
+
{formatPrimeAPIShipmentAddress(shipment.ppmShipment.tertiaryDestinationAddress)}
+
PPM SIT Expected:
diff --git a/src/components/PrimeUI/Shipment/Shipment.test.jsx b/src/components/PrimeUI/Shipment/Shipment.test.jsx index a1f528a46eb..85edaf6d209 100644 --- a/src/components/PrimeUI/Shipment/Shipment.test.jsx +++ b/src/components/PrimeUI/Shipment/Shipment.test.jsx @@ -60,13 +60,43 @@ const approvedMoveTaskOrder = { requiredDeliveryDate: null, scheduledPickupDate: '2020-03-16', secondaryDeliveryAddress: { - city: null, - postalCode: null, - state: null, - streetAddress1: null, + city: 'Fairfield', + id: 'bfe61147-5fd7-426e-b473-54ccf77bde35', + postalCode: '94535', + state: 'CA', + streetAddress1: '986 Any Avenue', + streetAddress2: 'P.O. Box 9876', + streetAddress3: 'c/o Some Person', + }, + secondaryPickupAddress: { + city: 'Beverly Hills', + id: 'cf159eca-162c-4131-84a0-795e684416a6', + postalCode: '90210', + state: 'CA', + streetAddress1: '124 Any Street', + streetAddress2: 'P.O. Box 12345', + streetAddress3: 'c/o Some Person', }, shipmentType: 'HHG', status: 'APPROVED', + tertiaryDeliveryAddress: { + city: 'Fairfield', + id: 'bfe61147-5fd7-426e-b473-54ccf77bde35', + postalCode: '94535', + state: 'CA', + streetAddress1: '985 Any Avenue', + streetAddress2: 'P.O. Box 9876', + streetAddress3: 'c/o Some Person', + }, + tertiaryPickupAddress: { + city: 'Beverly Hills', + id: 'cf159eca-162c-4131-84a0-795e684416a6', + postalCode: '90210', + state: 'CA', + streetAddress1: '125 Any Street', + streetAddress2: 'P.O. Box 12345', + streetAddress3: 'c/o Some Person', + }, updatedAt: '2021-10-22', mtoServiceItems: null, reweigh: { @@ -100,7 +130,7 @@ describe('Shipment details component', () => { expect(addServiceItemLink).toBeInTheDocument(); expect(addServiceItemLink.getAttribute('href')).toBe(`/shipments/${shipmentId}/service-items/new`); - expect(screen.queryAllByRole('link', { name: 'Edit' })).toHaveLength(3); + expect(screen.queryAllByRole('link', { name: 'Edit' })).toHaveLength(7); }); it('renders the shipment address values', async () => { @@ -108,7 +138,11 @@ describe('Shipment details component', () => { const shipment = approvedMoveTaskOrder.moveTaskOrder.mtoShipments[0]; expect(screen.getByText(formatPrimeAPIFullAddress(shipment.pickupAddress))).toBeInTheDocument(); + expect(screen.getByText(formatPrimeAPIFullAddress(shipment.secondaryPickupAddress))).toBeInTheDocument(); + expect(screen.getByText(formatPrimeAPIFullAddress(shipment.tertiaryPickupAddress))).toBeInTheDocument(); expect(screen.getByText(formatPrimeAPIFullAddress(shipment.destinationAddress))).toBeInTheDocument(); + expect(screen.getByText(formatPrimeAPIFullAddress(shipment.secondaryDeliveryAddress))).toBeInTheDocument(); + expect(screen.getByText(formatPrimeAPIFullAddress(shipment.tertiaryDeliveryAddress))).toBeInTheDocument(); }); it('renders the shipment info', () => { diff --git a/src/components/Table/TableCSVExportButton.jsx b/src/components/Table/TableCSVExportButton.jsx index 813f5b7a50d..5d3180ede70 100644 --- a/src/components/Table/TableCSVExportButton.jsx +++ b/src/components/Table/TableCSVExportButton.jsx @@ -1,10 +1,12 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useContext } from 'react'; import { CSVLink } from 'react-csv'; -import { Link } from '@trussworks/react-uswds'; +import { Button } from '@trussworks/react-uswds'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import moment from 'moment'; import PropTypes from 'prop-types'; +import SelectedGblocContext from 'components/Office/GblocSwitcher/SelectedGblocContext'; + const TableCSVExportButton = ({ labelText, filePrefix, @@ -16,12 +18,16 @@ const TableCSVExportButton = ({ paramSort, paramFilters, className, + isHeadquartersUser, }) => { const [isLoading, setIsLoading] = useState(false); const [csvRows, setCsvRows] = useState([]); const csvLinkRef = useRef(null); const { id: sortColumn, desc: sortOrder } = paramSort.length ? paramSort[0] : {}; + const gblocContext = useContext(SelectedGblocContext); + const { selectedGbloc } = isHeadquartersUser && gblocContext ? gblocContext : { selectedGbloc: undefined }; + const formatDataForExport = (data, columns = tableColumns) => { const formattedData = []; data.forEach((row) => { @@ -50,6 +56,7 @@ const TableCSVExportButton = ({ order: sortOrder ? 'desc' : 'asc', filters: paramFilters, currentPageSize: totalCount, + viewAsGBLOC: selectedGbloc, }); const formattedData = formatDataForExport(response[queueFetcherKey]); @@ -61,15 +68,23 @@ const TableCSVExportButton = ({ return (

- + @@ -96,6 +111,8 @@ TableCSVExportButton.propTypes = { paramSort: PropTypes.array, // paramSort is the filter columns and values currently applied to the queue paramFilters: PropTypes.array, + // isHeadquartersUser identifies if the active role is a headquarters user to allow switching GBLOCs + isHeadquartersUser: PropTypes.bool, }; TableCSVExportButton.defaultProps = { @@ -104,6 +121,7 @@ TableCSVExportButton.defaultProps = { hiddenColumns: [], paramSort: [], paramFilters: [], + isHeadquartersUser: false, }; export default TableCSVExportButton; diff --git a/src/components/Table/TableCSVExportButton.test.jsx b/src/components/Table/TableCSVExportButton.test.jsx index a3847752000..da8b2102f13 100644 --- a/src/components/Table/TableCSVExportButton.test.jsx +++ b/src/components/Table/TableCSVExportButton.test.jsx @@ -55,6 +55,11 @@ const paymentRequestsResponse = { ], }; +const paymentRequestsNoResultsResponse = { + page: 1, + perPage: 10, +}; + const paymentRequestColumns = [ { Header: ' ', @@ -129,6 +134,9 @@ const paymentRequestColumns = [ jest.mock('services/ghcApi', () => ({ getPaymentRequestsQueue: jest.fn().mockImplementation(() => Promise.resolve(paymentRequestsResponse)), + getPaymentRequestsNoResultsQueue: jest + .fn() + .mockImplementation(() => Promise.resolve(paymentRequestsNoResultsResponse)), })); describe('TableCSVExportButton', () => { @@ -155,4 +163,22 @@ describe('TableCSVExportButton', () => { expect(getPaymentRequestsQueue).toBeCalled(); }); + + const noResultsProps = { + tableColumns: paymentRequestColumns, + queueFetcher: () => Promise.resolve(paymentRequestsNoResultsResponse), + queueFetcherKey: 'queuePaymentRequests', + totalCount: 0, + }; + + it('is diabled when there is nothing to export', () => { + act(() => { + const wrapper = mount(); + const exportButton = wrapper.find('span[data-test-id="csv-export-btn-text"]'); + exportButton.simulate('click'); + wrapper.update(); + }); + + expect(getPaymentRequestsQueue).toBeCalled(); + }); }); diff --git a/src/components/Table/TableQueue.jsx b/src/components/Table/TableQueue.jsx index 5182fed428d..d09605914c4 100644 --- a/src/components/Table/TableQueue.jsx +++ b/src/components/Table/TableQueue.jsx @@ -48,6 +48,7 @@ const TableQueue = ({ csvExportQueueFetcher, csvExportQueueFetcherKey, sessionStorageKey, + isHeadquartersUser, }) => { const [isPageReload, setIsPageReload] = useState(true); useEffect(() => { @@ -87,7 +88,7 @@ const TableQueue = ({ const { id, desc } = paramSort.length ? paramSort[0] : {}; const gblocContext = useContext(SelectedGblocContext); - const { selectedGbloc } = gblocContext || { selectedGbloc: undefined }; + const { selectedGbloc } = isHeadquartersUser && gblocContext ? gblocContext : { selectedGbloc: undefined }; const multiSelectValueDelimiter = ','; @@ -312,6 +313,7 @@ const TableQueue = ({ totalCount={totalCount} paramSort={paramSort} paramFilters={paramFilters} + isHeadquartersUser={isHeadquartersUser} /> )}

@@ -381,6 +383,8 @@ TableQueue.propTypes = { csvExportQueueFetcherKey: PropTypes.string, // session storage key to store search filters sessionStorageKey: PropTypes.string, + // isHeadquartersUser identifies if the active role is a headquarters user to allow switching GBLOCs + isHeadquartersUser: PropTypes.bool, }; TableQueue.defaultProps = { @@ -399,5 +403,6 @@ TableQueue.defaultProps = { csvExportQueueFetcher: null, csvExportQueueFetcherKey: null, sessionStorageKey: 'default', + isHeadquartersUser: false, }; export default TableQueue; diff --git a/src/pages/MyMove/Profile/ContactInfo.jsx b/src/pages/MyMove/Profile/ContactInfo.jsx index 2b25f520797..298789034a0 100644 --- a/src/pages/MyMove/Profile/ContactInfo.jsx +++ b/src/pages/MyMove/Profile/ContactInfo.jsx @@ -37,13 +37,13 @@ export const ContactInfo = ({ serviceMember, updateServiceMember, userEmail }) = const payload = { id: serviceMember.id, telephone: values?.telephone, - secondary_telephone: values?.secondary_telephone, + secondary_telephone: values?.secondary_telephone || '', personal_email: values?.personal_email, phone_is_preferred: values?.phone_is_preferred, email_is_preferred: values?.email_is_preferred, }; - if (!payload.secondary_telephone) { - delete payload.secondary_telephone; + if (!payload.secondary_telephone || payload.secondary_telephone === '') { + payload.secondary_telephone = ''; } return patchServiceMember(payload) diff --git a/src/pages/MyMove/Profile/EditContactInfo.jsx b/src/pages/MyMove/Profile/EditContactInfo.jsx index aa27b30eae0..898031e46b8 100644 --- a/src/pages/MyMove/Profile/EditContactInfo.jsx +++ b/src/pages/MyMove/Profile/EditContactInfo.jsx @@ -73,9 +73,7 @@ export const EditContactInfo = ({ backup_mailing_address: values[backupAddressName.toString()], }; - if (values?.secondary_telephone) { - serviceMemberPayload.secondary_telephone = values?.secondary_telephone; - } + serviceMemberPayload.secondary_telephone = values?.secondary_telephone; const backupContactPayload = { id: currentBackupContacts[0].id, diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx index b8a6ed6a6fa..72f27a84ca5 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.jsx @@ -27,11 +27,14 @@ import { setFlashMessage as setFlashMessageAction } from 'store/flash/actions'; import { elevatedPrivilegeTypes } from 'constants/userPrivileges'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; import departmentIndicators from 'constants/departmentIndicators'; +import { generateUniqueDodid, generateUniqueEmplid } from 'utils/customer'; +import Hint from 'components/Hint'; export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const [serverError, setServerError] = useState(null); const [showEmplid, setShowEmplid] = useState(false); const [isSafetyMove, setIsSafetyMove] = useState(false); + const [showSafetyMoveHint, setShowSafetyMoveHint] = useState(false); const navigate = useNavigate(); const branchOptions = dropdownInputOptions(SERVICE_MEMBER_AGENCY_LABELS); @@ -42,6 +45,9 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const [isSafetyMoveFF, setSafetyMoveFF] = useState(false); + const uniqueDodid = generateUniqueDodid(); + const uniqueEmplid = generateUniqueEmplid(); + useEffect(() => { isBooleanFlagEnabled('safety_move')?.then((enabled) => { setSafetyMoveFF(enabled); @@ -55,6 +61,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const initialValues = { affiliation: '', edipi: '', + emplid: '', first_name: '', middle_name: '', last_name: '', @@ -87,7 +94,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { }, create_okta_account: '', cac_user: '', - is_safety_move: false, + is_safety_move: 'false', }; const handleBack = () => { @@ -144,10 +151,8 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const validationSchema = Yup.object().shape({ affiliation: Yup.mixed().oneOf(Object.keys(SERVICE_MEMBER_AGENCY_LABELS)).required('Required'), - edipi: Yup.string().matches(/[0-9]{10}/, 'Enter a 10-digit DOD ID number'), - emplid: Yup.string() - .notRequired() - .matches(/[0-9]{7}/, 'Enter a 7-digit EMPLID number'), + edipi: Yup.string().matches(/^(SM[0-9]{8}|[0-9]{10})$/, 'Enter a 10-digit DOD ID number'), + emplid: Yup.string().matches(/^(SM[0-9]{5}|[0-9]{7})$/, 'Enter a 7-digit EMPLID number'), first_name: Yup.string().required('Required'), middle_name: Yup.string(), last_name: Yup.string().required('Required'), @@ -193,11 +198,10 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { const { value } = e.target; if (value === 'true') { setIsSafetyMove(true); - // clear out DoDID, emplid, and OKTA fields + setShowSafetyMoveHint(true); setValues({ ...values, - edipi: '', - emplid: '', + affiliation: '', create_okta_account: '', cac_user: 'true', is_safety_move: 'true', @@ -206,15 +210,47 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { setIsSafetyMove(false); setValues({ ...values, + affiliation: '', + edipi: '', + emplid: '', is_safety_move: 'false', }); } }; const handleBranchChange = (e) => { - if (e.target.value === departmentIndicators.COAST_GUARD) { + setShowSafetyMoveHint(false); + if (e.target.value === departmentIndicators.COAST_GUARD && isSafetyMove) { setShowEmplid(true); + setValues({ + ...values, + affiliation: e.target.value, + edipi: uniqueDodid, + emplid: uniqueEmplid, + }); + } else if (e.target.value === departmentIndicators.COAST_GUARD && !isSafetyMove) { + setShowEmplid(true); + setValues({ + ...values, + affiliation: e.target.value, + edipi: '', + emplid: '', + }); + } else if (e.target.value !== departmentIndicators.COAST_GUARD && isSafetyMove) { + setShowEmplid(false); + setValues({ + ...values, + affiliation: e.target.value, + edipi: uniqueDodid, + emplid: '', + }); } else { setShowEmplid(false); + setValues({ + ...values, + affiliation: e.target.value, + edipi: '', + emplid: '', + }); } }; return ( @@ -234,6 +270,7 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { value="true" data-testid="is-safety-move-yes" onChange={handleIsSafetyMove} + checked={values.is_safety_move === 'true'} /> { value="false" data-testid="is-safety-move-no" onChange={handleIsSafetyMove} + checked={values.is_safety_move === 'false'} /> @@ -262,9 +300,9 @@ export const CreateCustomerForm = ({ userPrivileges, setFlashMessage }) => { label="DoD ID number" name="edipi" id="edipi" - labelHint="Optional" maxLength="10" isDisabled={isSafetyMove} + data-testid="edipiInput" /> {showEmplid && ( { name="emplid" id="emplid" maxLength="7" - labelHint="Optional" inputMode="numeric" pattern="[0-9]{7}" isDisabled={isSafetyMove} + data-testid="emplidInput" /> )} + {isSafetyMove && showSafetyMoveHint && ( + + Once a branch is selected, this will generate a random safety move identifier + + )}

Customer Name

diff --git a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx index 7167c40da84..088b20f415e 100644 --- a/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx +++ b/src/pages/Office/CustomerOnboarding/CreateCustomerForm.test.jsx @@ -69,7 +69,7 @@ const fakePayload = { }, create_okta_account: 'true', cac_user: 'false', - is_safety_move: 'false', + is_safety_move: false, }; const fakeResponse = { @@ -215,6 +215,66 @@ describe('CreateCustomerForm', () => { expect(screen.getByText('EMPLID')).toBeInTheDocument(); }); + it('payload can have an empty secondary phone number', async () => { + createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); + + const { getByLabelText, getByTestId, getByRole } = render( + + + , + ); + + const user = userEvent.setup(); + + const saveBtn = await screen.findByRole('button', { name: 'Save' }); + expect(saveBtn).toBeInTheDocument(); + + await user.selectOptions(getByLabelText('Branch of service'), [fakePayload.affiliation]); + + await user.type(getByLabelText('First name'), fakePayload.first_name); + await user.type(getByLabelText('Last name'), fakePayload.last_name); + + await user.type(getByLabelText('Best contact phone'), fakePayload.telephone); + await user.type(getByLabelText('Personal email'), fakePayload.personal_email); + + await user.type(getByTestId('res-add-street1'), fakePayload.residential_address.streetAddress1); + await user.type(getByTestId('res-add-city'), fakePayload.residential_address.city); + await user.selectOptions(getByTestId('res-add-state'), [fakePayload.residential_address.state]); + await user.type(getByTestId('res-add-zip'), fakePayload.residential_address.postalCode); + + await user.type(getByTestId('backup-add-street1'), fakePayload.backup_mailing_address.streetAddress1); + await user.type(getByTestId('backup-add-city'), fakePayload.backup_mailing_address.city); + await user.selectOptions(getByTestId('backup-add-state'), [fakePayload.backup_mailing_address.state]); + await user.type(getByTestId('backup-add-zip'), fakePayload.backup_mailing_address.postalCode); + + await user.type(getByLabelText('Name'), fakePayload.backup_contact.name); + await user.type(getByRole('textbox', { name: 'Email' }), fakePayload.backup_contact.email); + await user.type(getByRole('textbox', { name: 'Phone' }), fakePayload.backup_contact.telephone); + + await userEvent.type(getByTestId('create-okta-account-yes'), fakePayload.create_okta_account); + + await userEvent.type(getByTestId('cac-user-no'), fakePayload.cac_user); + + await waitFor(() => { + expect(saveBtn).toBeEnabled(); + }); + + const waiter = waitFor(() => { + expect(createCustomerWithOktaOption).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith(ordersPath, { + state: { + isSafetyMoveSelected: false, + }, + }); + }); + + await user.click(saveBtn); + await waiter; + expect(mockNavigate).toHaveBeenCalled(); + + expect(createCustomerWithOktaOption.mock.calls[0][0]).not.toHaveProperty('secondary_number'); + }, 10000); + it('navigates the user on cancel click', async () => { const { getByText } = render( @@ -335,6 +395,71 @@ describe('CreateCustomerForm', () => { }); }, 10000); + it('disables and populates DODID and EMPLID inputs when safety move is selected', async () => { + createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); + isBooleanFlagEnabled.mockImplementation(() => Promise.resolve(true)); + + const { getByLabelText, getByTestId, getByRole } = render( + + + , + ); + + const user = userEvent.setup(); + + const safetyMove = await screen.findByTestId('is-safety-move-no'); + expect(safetyMove).toBeChecked(); + + // check the safety move box + await userEvent.type(getByTestId('is-safety-move-yes'), safetyPayload.is_safety_move); + + expect(await screen.findByTestId('safetyMoveHint')).toBeInTheDocument(); + + await user.selectOptions(getByLabelText('Branch of service'), ['COAST_GUARD']); + + // the input boxes should now be disabled + expect(await screen.findByTestId('edipiInput')).toBeDisabled(); + expect(await screen.findByTestId('emplidInput')).toBeDisabled(); + + // should be able to submit the form + await user.type(getByLabelText('First name'), safetyPayload.first_name); + await user.type(getByLabelText('Last name'), safetyPayload.last_name); + + await user.type(getByLabelText('Best contact phone'), safetyPayload.telephone); + await user.type(getByLabelText('Personal email'), safetyPayload.personal_email); + + await userEvent.type(getByTestId('res-add-street1'), safetyPayload.residential_address.streetAddress1); + await userEvent.type(getByTestId('res-add-city'), safetyPayload.residential_address.city); + await userEvent.selectOptions(getByTestId('res-add-state'), [safetyPayload.residential_address.state]); + await userEvent.type(getByTestId('res-add-zip'), safetyPayload.residential_address.postalCode); + + await userEvent.type(getByTestId('backup-add-street1'), safetyPayload.backup_mailing_address.streetAddress1); + await userEvent.type(getByTestId('backup-add-city'), safetyPayload.backup_mailing_address.city); + await userEvent.selectOptions(getByTestId('backup-add-state'), [safetyPayload.backup_mailing_address.state]); + await userEvent.type(getByTestId('backup-add-zip'), safetyPayload.backup_mailing_address.postalCode); + + await userEvent.type(getByLabelText('Name'), safetyPayload.backup_contact.name); + await userEvent.type(getByRole('textbox', { name: 'Email' }), safetyPayload.backup_contact.email); + await userEvent.type(getByRole('textbox', { name: 'Phone' }), safetyPayload.backup_contact.telephone); + + const saveBtn = await screen.findByRole('button', { name: 'Save' }); + expect(saveBtn).toBeInTheDocument(); + + await waitFor(() => { + expect(saveBtn).toBeEnabled(); + }); + await userEvent.click(saveBtn); + + await waitFor(() => { + expect(createCustomerWithOktaOption).toHaveBeenCalled(); + expect(mockNavigate).toHaveBeenCalledWith(ordersPath, { + state: { + isSafetyMoveSelected: true, + }, + }); + }); + }, 10000); + it('submits the form and tests for unsupported state validation', async () => { createCustomerWithOktaOption.mockImplementation(() => Promise.resolve(fakeResponse)); diff --git a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx index 9c6f613cc7e..8ba085fc464 100644 --- a/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx +++ b/src/pages/Office/HeadquartersQueues/HeadquartersQueues.jsx @@ -247,6 +247,7 @@ const HeadquartersQueue = () => { csvExportQueueFetcher={getMovesQueue} csvExportQueueFetcherKey="queueMoves" sessionStorageKey={queueType} + isHeadquartersUser /> ); @@ -273,6 +274,7 @@ const HeadquartersQueue = () => { csvExportQueueFetcher={getPaymentRequestsQueue} csvExportQueueFetcherKey="queuePaymentRequests" sessionStorageKey={queueType} + isHeadquartersUser /> ); @@ -299,6 +301,7 @@ const HeadquartersQueue = () => { csvExportQueueFetcher={getServicesCounselingPPMQueue} csvExportQueueFetcherKey="queueMoves" sessionStorageKey={queueType} + isHeadquartersUser /> ); @@ -326,6 +329,7 @@ const HeadquartersQueue = () => { csvExportQueueFetcher={getServicesCounselingQueue} csvExportQueueFetcherKey="queueMoves" sessionStorageKey={queueType} + isHeadquartersUser /> ); diff --git a/src/pages/Office/MoveDetails/MoveDetails.jsx b/src/pages/Office/MoveDetails/MoveDetails.jsx index 8d6a4c359ac..e2fb69d7450 100644 --- a/src/pages/Office/MoveDetails/MoveDetails.jsx +++ b/src/pages/Office/MoveDetails/MoveDetails.jsx @@ -57,6 +57,8 @@ const MoveDetails = ({ setExcessWeightRiskCount, setUnapprovedSITExtensionCount, setShipmentsWithDeliveryAddressUpdateRequestedCount, + missingOrdersInfoCount, + setMissingOrdersInfoCount, isMoveLocked, }) => { const { moveCode } = useParams(); @@ -238,6 +240,28 @@ const MoveDetails = ({ setShipmentMissingRequiredInformation(shipmentIsMissingInformation); }, [mtoShipments]); + // using useMemo here due to this being used in a useEffect + // using useMemo prevents the useEffect from being rendered on ever render by memoizing the object + // so that it only recognizes the change when the orders object changes + const requiredOrdersInfo = useMemo( + () => ({ + ordersNumber: order?.order_number || '', + ordersType: order?.order_type || '', + ordersTypeDetail: order?.order_type_detail || '', + tacMDC: order?.tac || '', + departmentIndicator: order?.department_indicator || '', + }), + [order], + ); + + // Keep num of missing orders info synced up + useEffect(() => { + const ordersInfoCount = Object.values(requiredOrdersInfo).reduce((count, value) => { + return !value ? count + 1 : count; + }, 0); + setMissingOrdersInfoCount(ordersInfoCount); + }, [order, requiredOrdersInfo, setMissingOrdersInfoCount]); + if (isLoading) return ; if (isError) return ; @@ -284,7 +308,7 @@ const MoveDetails = ({ const customerInfo = { name: formattedCustomerName(customer.last_name, customer.first_name, customer.suffix, customer.middle_name), agency: customer.agency, - dodId: customer.dodID, + edipi: customer.edipi, emplid: customer.emplid, phone: customer.phone, altPhone: customer.secondaryTelephone, @@ -294,13 +318,6 @@ const MoveDetails = ({ backupContact: customer.backup_contact, }; - const requiredOrdersInfo = { - ordersNumber: order.order_number, - ordersType: order.order_type, - ordersTypeDetail: order.order_type_detail, - tacMDC: order.tac, - }; - const hasMissingOrdersRequiredInfo = Object.values(requiredOrdersInfo).some((value) => !value || value === ''); const hasAmendedOrders = ordersInfo.uploadedAmendedOrderID && !ordersInfo.amendedOrdersAcknowledgedAt; const hasDestinationAddressUpdate = @@ -311,12 +328,12 @@ const MoveDetails = ({
- + {missingOrdersInfoCount} ({ ...jest.requireActual('react-router-dom'), @@ -360,6 +361,7 @@ const requestedMoveDetailsAmendedOrdersQuery = { }, order: { id: '1', + department_indicator: 'ARMY', originDutyLocation: { address: { streetAddress1: '', @@ -783,6 +785,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -801,6 +805,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -819,6 +825,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -885,6 +893,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -905,6 +915,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -928,6 +940,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -967,6 +981,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedServiceItemCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -986,12 +1002,15 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={2} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); it('renders an error indicator in the sidebar', () => { expect(wrapper.find('a[href="#orders"] span[data-testid="tag"]').exists()).toBe(true); + expect(wrapper.find('a[href="#orders"] span[data-testid="tag"]').text()).toBe('2'); }); }); @@ -1006,6 +1025,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -1025,6 +1046,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); @@ -1039,6 +1062,7 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount, setExcessWeightRiskCount, setUnapprovedSITExtensionCount, + setMissingOrdersInfoCount, }; it('renders the financial review flag button when user has permission', async () => { @@ -1150,6 +1174,8 @@ describe('MoveDetails page', () => { setUnapprovedServiceItemCount={setUnapprovedServiceItemCount} setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnapprovedSITExtensionCount} + missingOrdersInfoCount={0} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} /> , ); diff --git a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx index 6c065a37083..104aac73b43 100644 --- a/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx +++ b/src/pages/Office/ServicesCounselingMoveDetails/ServicesCounselingMoveDetails.jsx @@ -1,7 +1,6 @@ import React, { useState, useEffect, useMemo } from 'react'; import { Link, useParams, useNavigate, generatePath } from 'react-router-dom'; import { useQueryClient, useMutation } from '@tanstack/react-query'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { func } from 'prop-types'; import classnames from 'classnames'; import 'styles/office.scss'; @@ -42,7 +41,13 @@ import { objectIsMissingFieldWithCondition } from 'utils/displayFlags'; import { ReviewButton } from 'components/form/IconButtons'; import { calculateWeightRequested } from 'hooks/custom'; -const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCount, isMoveLocked }) => { +const ServicesCounselingMoveDetails = ({ + infoSavedAlert, + setUnapprovedShipmentCount, + isMoveLocked, + missingOrdersInfoCount, + setMissingOrdersInfoCount, +}) => { const { moveCode } = useParams(); const navigate = useNavigate(); const [alertMessage, setAlertMessage] = useState(null); @@ -345,6 +350,20 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo ntsSac: order.ntsSac, }; + // using useMemo here due to this being used in a useEffect + // using useMemo prevents the useEffect from being rendered on ever render by memoizing the object + // so that it only recognizes the change when the orders object changes + const requiredOrdersInfo = useMemo( + () => ({ + ordersNumber: order?.order_number || '', + ordersType: order?.order_type || '', + ordersTypeDetail: order?.order_type_detail || '', + tacMDC: order?.tac || '', + departmentIndicator: order?.department_indicator || '', + }), + [order], + ); + const handleButtonDropdownChange = (e) => { const selectedOption = e.target.value; @@ -412,6 +431,14 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo setUnapprovedShipmentCount, ]); + // Keep num of missing orders info synced up + useEffect(() => { + const ordersInfoCount = Object.values(requiredOrdersInfo).reduce((count, value) => { + return !value ? count + 1 : count; + }, 0); + setMissingOrdersInfoCount(ordersInfoCount); + }, [order, requiredOrdersInfo, setMissingOrdersInfoCount]); + if (isLoading) return ; if (isError) return ; @@ -454,13 +481,6 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo return false; }; - const requiredOrdersInfo = { - ordersNumber: order.order_number, - ordersType: order.order_type, - ordersTypeDetail: order.order_type_detail, - tacMDC: order.tac, - }; - const allShipmentsDeleted = mtoShipments.every((shipment) => !!shipment.deletedAt); const hasMissingOrdersRequiredInfo = Object.values(requiredOrdersInfo).some((value) => !value || value === ''); const hasAmendedOrders = ordersInfo.uploadedAmendedOrderID && !ordersInfo.amendedOrdersAcknowledgedAt; @@ -477,12 +497,12 @@ const ServicesCounselingMoveDetails = ({ infoSavedAlert, setUnapprovedShipmentCo {shipmentConcernCount} - + {missingOrdersInfoCount} { return render( - + , ); }; @@ -570,6 +638,23 @@ describe('MoveDetails page', () => { expect(mockSetUpapprovedShipmentCount).toHaveBeenCalledWith(3); }); + it('shares the number of missing orders information', () => { + const moveDetailsQuery = { + ...newMoveDetailsQuery, + order: orderMissingRequiredInfo, + }; + + useMoveDetailsQueries.mockReturnValue(moveDetailsQuery); + useOrdersDocumentQueries.mockReturnValue(moveDetailsQuery); + + const mockSetMissingOrdersInfoCount = jest.fn(); + renderComponent({ setMissingOrdersInfoCount: mockSetMissingOrdersInfoCount }); + + // Should have called `setMissingOrdersInfoCount` with 4 missing fields + expect(mockSetMissingOrdersInfoCount).toHaveBeenCalledTimes(1); + expect(mockSetMissingOrdersInfoCount).toHaveBeenCalledWith(4); + }); + /* eslint-disable camelcase */ it('renders shipments info', async () => { useMoveDetailsQueries.mockReturnValue(newMoveDetailsQuery); @@ -836,7 +921,11 @@ describe('MoveDetails page', () => { permissions={[permissionTypes.updateShipment, permissionTypes.updateCustomer]} {...mockRoutingOptions} > - + , ); @@ -933,7 +1022,11 @@ describe('MoveDetails page', () => { path={servicesCounselingRoutes.BASE_SHIPMENT_ADD_PATH} params={{ moveCode: mockRequestedMoveCode, shipmentType }} > - , + + , , ); @@ -1031,7 +1124,10 @@ describe('MoveDetails page', () => { it('renders the financial review flag button when user has permission', async () => { render( - + , ); diff --git a/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx b/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx index 8646b252640..eaedf6eca8a 100644 --- a/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx +++ b/src/pages/Office/ServicesCounselingMoveInfo/ServicesCounselingMoveInfo.jsx @@ -42,6 +42,7 @@ const ServicesCounselingMoveInfo = () => { const [unapprovedServiceItemCount, setUnapprovedServiceItemCount] = React.useState(0); const [excessWeightRiskCount, setExcessWeightRiskCount] = React.useState(0); const [unapprovedSITExtensionCount, setUnApprovedSITExtensionCount] = React.useState(0); + const [missingOrdersInfoCount, setMissingOrdersInfoCount] = useState(0); const [infoSavedAlert, setInfoSavedAlert] = useState(null); const { hasRecentError, traceId } = useSelector((state) => state.interceptor); const [moveLockFlag, setMoveLockFlag] = useState(false); @@ -195,6 +196,7 @@ const ServicesCounselingMoveInfo = () => { unapprovedServiceItemCount={unapprovedServiceItemCount} excessWeightRiskCount={excessWeightRiskCount} unapprovedSITExtensionCount={unapprovedSITExtensionCount} + missingOrdersInfoCount={missingOrdersInfoCount} /> )} @@ -214,6 +216,8 @@ const ServicesCounselingMoveInfo = () => { } diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx index 902ac0aae5f..398bc9fe860 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.jsx @@ -184,6 +184,10 @@ export const counselingColumns = (moveLockFlag, originLocationList, supervisor) return row.originDutyLocation?.name; }, }), + createHeader('Counseling office', 'counselingOffice', { + id: 'counselingOffice', + isFilterable: true, + }), ]; export const closeoutColumns = (moveLockFlag, ppmCloseoutGBLOC, ppmCloseoutOriginLocationList, supervisor) => [ createHeader( diff --git a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx index 7e41c292700..04f43e99710 100644 --- a/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx +++ b/src/pages/Office/ServicesCounselingQueue/ServicesCounselingQueue.test.jsx @@ -117,6 +117,7 @@ const needsCounselingMoves = { name: 'Los Alamos', }, originGBLOC: 'LKNQ', + counselingOffice: '', }, { id: 'move3', @@ -178,6 +179,7 @@ const serviceCounselingCompletedMoves = { name: 'Los Alamos', }, originGBLOC: 'LKNQ', + counselingOffice: '67592323-fc7e-4b35-83a7-57faa53b7acf', }, ], }, diff --git a/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx b/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx index a931a110286..bbe6c9c4f09 100644 --- a/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx +++ b/src/pages/Office/TXOMoveInfo/TXOMoveInfo.jsx @@ -39,6 +39,7 @@ const TXOMoveInfo = () => { const [excessWeightRiskCount, setExcessWeightRiskCount] = React.useState(0); const [pendingPaymentRequestCount, setPendingPaymentRequestCount] = React.useState(0); const [unapprovedSITExtensionCount, setUnApprovedSITExtensionCount] = React.useState(0); + const [missingOrdersInfoCount, setMissingOrdersInfoCount] = useState(0); const [moveLockFlag, setMoveLockFlag] = useState(false); const [isMoveLocked, setIsMoveLocked] = useState(false); @@ -150,6 +151,7 @@ const TXOMoveInfo = () => { excessWeightRiskCount={excessWeightRiskCount} pendingPaymentRequestCount={pendingPaymentRequestCount} unapprovedSITExtensionCount={unapprovedSITExtensionCount} + missingOrdersInfoCount={missingOrdersInfoCount} moveCode={moveCode} reportId={reportId} order={order} @@ -177,6 +179,8 @@ const TXOMoveInfo = () => { } setExcessWeightRiskCount={setExcessWeightRiskCount} setUnapprovedSITExtensionCount={setUnApprovedSITExtensionCount} + missingOrdersInfoCount={missingOrdersInfoCount} + setMissingOrdersInfoCount={setMissingOrdersInfoCount} isMoveLocked={isMoveLocked} /> } diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx index e581487702c..699700e7ee8 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdate.jsx @@ -122,7 +122,11 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { const editableProGearWeightActualField = true; const editableSpouseProGearWeightActualField = true; const reformatPrimeApiPickupAddress = fromPrimeAPIAddressFormat(shipment.pickupAddress); + const reformatPrimeApiSecondaryPickupAddress = fromPrimeAPIAddressFormat(shipment.secondaryPickupAddress); + const reformatPrimeApiTertiaryPickupAddress = fromPrimeAPIAddressFormat(shipment.tertiaryPickupAddress); const reformatPrimeApiDestinationAddress = fromPrimeAPIAddressFormat(shipment.destinationAddress); + const reformatPrimeApiSecondaryDeliveryAddress = fromPrimeAPIAddressFormat(shipment.secondaryDeliveryAddress); + const reformatPrimeApiTertiaryDeliveryAddress = fromPrimeAPIAddressFormat(shipment.tertiaryDeliveryAddress); const editablePickupAddress = isEmpty(reformatPrimeApiPickupAddress); const editableDestinationAddress = isEmpty(reformatPrimeApiDestinationAddress); @@ -309,7 +313,11 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { scheduledDeliveryDate: shipment.scheduledDeliveryDate, actualDeliveryDate: shipment.actualDeliveryDate, pickupAddress: editablePickupAddress ? emptyAddress : reformatPrimeApiPickupAddress, + secondaryPickupAddress: reformatPrimeApiSecondaryPickupAddress, + tertiaryPickupAddress: reformatPrimeApiTertiaryPickupAddress, destinationAddress: editableDestinationAddress ? emptyAddress : reformatPrimeApiDestinationAddress, + secondaryDeliveryAddress: reformatPrimeApiSecondaryDeliveryAddress, + tertiaryDeliveryAddress: reformatPrimeApiTertiaryDeliveryAddress, destinationType: shipment.destinationType, diversion: shipment.diversion, }; @@ -364,7 +372,11 @@ const PrimeUIShipmentUpdate = ({ setFlashMessage }) => { actualSpouseProGearWeight={initialValues.actualSpouseProGearWeight} requestedPickupDate={initialValues.requestedPickupDate} pickupAddress={initialValues.pickupAddress} + secondaryPickupAddress={initialValues.secondaryPickupAddress} + tertiaryPickupAddress={initialValues.tertiaryPickupAddress} destinationAddress={initialValues.destinationAddress} + secondaryDeliveryAddress={initialValues.secondaryDeliveryAddress} + tertiaryDeliveryAddress={initialValues.tertiaryDeliveryAddress} diversion={initialValues.diversion} /> )} diff --git a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx index 0c71eb62549..50d8bdb2a57 100644 --- a/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx +++ b/src/pages/PrimeUI/Shipment/PrimeUIShipmentUpdateForm.jsx @@ -35,7 +35,11 @@ const PrimeUIShipmentUpdateForm = ({ actualProGearWeight, actualSpouseProGearWeight, pickupAddress, + secondaryPickupAddress, + tertiaryPickupAddress, destinationAddress, + secondaryDeliveryAddress, + tertiaryDeliveryAddress, }) => { return ( @@ -142,9 +146,17 @@ const PrimeUIShipmentUpdateForm = ({
Pickup Address
{editablePickupAddress && } {!editablePickupAddress && formatAddress(pickupAddress)} +
Second Pickup Address
+ {formatAddress(secondaryPickupAddress)} +
Third Pickup Address
+ {formatAddress(tertiaryPickupAddress)}
Destination Address
{editableDestinationAddress && } {!editableDestinationAddress && formatAddress(destinationAddress)} +
Second Destination Address
+ {formatAddress(secondaryDeliveryAddress)} +
Third Destination Address
+ {formatAddress(tertiaryDeliveryAddress)} { return generalRoutes.HOME_PATH; } }; + +export const generateUniqueDodid = () => { + const prefix = 'SM'; + + // Custom epoch start date (e.g., 2024-01-01), generates something like 1704067200000 + const customEpoch = new Date('2024-01-01').getTime(); + const now = Date.now(); + + // Calculate milliseconds since custom epoch, then convert to an 8-digit integer + const uniqueNumber = Math.floor((now - customEpoch) / 1000); // Dividing by 1000 to reduce to seconds + + // Convert the unique number to a string, ensuring it has 8 digits + const uniqueStr = uniqueNumber.toString().slice(0, 8).padStart(8, '0'); + + return prefix + uniqueStr; +}; + +export const generateUniqueEmplid = () => { + const prefix = 'SM'; + const customEpoch = new Date('2024-01-01').getTime(); + const now = Date.now(); + const uniqueNumber = Math.floor((now - customEpoch) / 1000) % 100000; // Modulo 100000 ensures it's 5 digits + const uniqueStr = uniqueNumber.toString().padStart(5, '0'); + return prefix + uniqueStr; +}; diff --git a/swagger-def/definitions/ServiceItemParamName.yaml b/swagger-def/definitions/ServiceItemParamName.yaml index 066dd32d45e..c3361653fbe 100644 --- a/swagger-def/definitions/ServiceItemParamName.yaml +++ b/swagger-def/definitions/ServiceItemParamName.yaml @@ -69,3 +69,4 @@ enum: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents diff --git a/swagger-def/definitions/prime/v3/MTOShipmentWithoutServiceItems.yaml b/swagger-def/definitions/prime/v3/MTOShipmentWithoutServiceItems.yaml index 4adad7ff907..dfd4710c6aa 100644 --- a/swagger-def/definitions/prime/v3/MTOShipmentWithoutServiceItems.yaml +++ b/swagger-def/definitions/prime/v3/MTOShipmentWithoutServiceItems.yaml @@ -166,13 +166,13 @@ properties: destinationType: $ref: '../../DestinationType.yaml' secondaryPickupAddress: - description: A second pickup address for this shipment, if the customer entered one. An optional field. - allOf: - - $ref: '../../Address.yaml' + $ref: '../../Address.yaml' secondaryDeliveryAddress: - description: A second delivery address for this shipment, if the customer entered one. An optional field. - allOf: - - $ref: '../../Address.yaml' + $ref: '../../Address.yaml' + tertiaryPickupAddress: + $ref: '../../Address.yaml' + tertiaryDeliveryAddress: + $ref: '../../Address.yaml' storageFacility: allOf: - x-nullable: true diff --git a/swagger-def/definitions/prime/v3/PPMShipment.yaml b/swagger-def/definitions/prime/v3/PPMShipment.yaml index 278aeac423e..20d29db9f97 100644 --- a/swagger-def/definitions/prime/v3/PPMShipment.yaml +++ b/swagger-def/definitions/prime/v3/PPMShipment.yaml @@ -62,6 +62,12 @@ properties: type: boolean x-omitempty: false x-nullable: true + tertiaryPickupAddress: + $ref: '../../Address.yaml' + hasTertiaryPickupAddress: + type: boolean + x-omitempty: false + x-nullable: true actualPickupPostalCode: description: > The actual postal code where the PPM shipment started. To be filled once the customer has moved the shipment. @@ -80,6 +86,12 @@ properties: type: boolean x-omitempty: false x-nullable: true + tertiaryDestinationAddress: + $ref: '../../Address.yaml' + hasTertiaryDestinationAddress: + type: boolean + x-omitempty: false + x-nullable: true actualDestinationPostalCode: description: > The actual postal code where the PPM shipment ended. To be filled once the customer has moved the shipment. diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 09bbc9c1f90..4b60dc576c2 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3267,6 +3267,7 @@ paths: closeoutInitiated, closeoutLocation, ppmStatus, + counselingOffice, ] description: field that results should be sorted by - in: query @@ -3286,6 +3287,10 @@ paths: name: lastName type: string description: filters using a prefix match on the service member's last name + - in: query + name: counselingOffice + type: string + description: filters using a counselingOffice name of the move - in: query name: dodID type: string @@ -4402,7 +4407,7 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 - dodID: + edipi: type: string userID: type: string @@ -6700,6 +6705,9 @@ definitions: ppmStatus: $ref: '#/definitions/PPMStatus' x-nullable: true + counselingOffice: + type: string + x-nullable: true QueueMovesResult: type: object properties: diff --git a/swagger-def/internal.yaml b/swagger-def/internal.yaml index 679236a358b..134d05f587a 100644 --- a/swagger-def/internal.yaml +++ b/swagger-def/internal.yaml @@ -705,7 +705,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: '^[2-9]\d{2}-\d{3}-\d{4}$' + pattern: '^([2-9]\d{2}-\d{3}-\d{4})?$' example: 212-555-5555 x-nullable: true title: Secondary Phone @@ -799,7 +799,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: '^[2-9]\d{2}-\d{3}-\d{4}$' + pattern: '^([2-9]\d{2}-\d{3}-\d{4})?$' example: 212-555-5555 x-nullable: true title: Alternate phone @@ -883,7 +883,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: '^[2-9]\d{2}-\d{3}-\d{4}$' + pattern: '^([2-9]\d{2}-\d{3}-\d{4})?$' example: 212-555-5555 x-nullable: true title: Alternate Phone diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 4440a907dc3..ce67ccb909d 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3388,6 +3388,7 @@ paths: - closeoutInitiated - closeoutLocation - ppmStatus + - counselingOffice description: field that results should be sorted by - in: query name: order @@ -3408,6 +3409,10 @@ paths: name: lastName type: string description: filters using a prefix match on the service member's last name + - in: query + name: counselingOffice + type: string + description: filters using a counselingOffice name of the move - in: query name: dodID type: string @@ -4594,7 +4599,7 @@ definitions: type: string format: uuid example: c56a4180-65aa-42ec-a945-5fd21dec0538 - dodID: + edipi: type: string userID: type: string @@ -6982,6 +6987,9 @@ definitions: ppmStatus: $ref: '#/definitions/PPMStatus' x-nullable: true + counselingOffice: + type: string + x-nullable: true QueueMovesResult: type: object properties: @@ -10307,6 +10315,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: diff --git a/swagger/internal.yaml b/swagger/internal.yaml index cc75bf8e2fa..80c0dd139ed 100644 --- a/swagger/internal.yaml +++ b/swagger/internal.yaml @@ -726,7 +726,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ example: 212-555-5555 x-nullable: true title: Secondary Phone @@ -820,7 +820,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ example: 212-555-5555 x-nullable: true title: Alternate phone @@ -904,7 +904,7 @@ definitions: secondary_telephone: type: string format: telephone - pattern: ^[2-9]\d{2}-\d{3}-\d{4}$ + pattern: ^([2-9]\d{2}-\d{3}-\d{4})?$ example: 212-555-5555 x-nullable: true title: Alternate Phone diff --git a/swagger/prime.yaml b/swagger/prime.yaml index f7f587b8624..6da8e27e845 100644 --- a/swagger/prime.yaml +++ b/swagger/prime.yaml @@ -3126,6 +3126,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: diff --git a/swagger/prime_v2.yaml b/swagger/prime_v2.yaml index 7e1bf6c0acc..def5f5626c3 100644 --- a/swagger/prime_v2.yaml +++ b/swagger/prime_v2.yaml @@ -1711,6 +1711,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: diff --git a/swagger/prime_v3.yaml b/swagger/prime_v3.yaml index 19edac26bea..77007b46f99 100644 --- a/swagger/prime_v3.yaml +++ b/swagger/prime_v3.yaml @@ -1735,6 +1735,7 @@ definitions: - StandaloneCrate - StandaloneCrateCap - UncappedRequestTotal + - LockedPriceCents ServiceItemParamType: type: string enum: @@ -2304,6 +2305,12 @@ definitions: type: boolean x-omitempty: false x-nullable: true + tertiaryPickupAddress: + $ref: '#/definitions/Address' + hasTertiaryPickupAddress: + type: boolean + x-omitempty: false + x-nullable: true actualPickupPostalCode: description: > The actual postal code where the PPM shipment started. To be filled @@ -2323,6 +2330,12 @@ definitions: type: boolean x-omitempty: false x-nullable: true + tertiaryDestinationAddress: + $ref: '#/definitions/Address' + hasTertiaryDestinationAddress: + type: boolean + x-omitempty: false + x-nullable: true actualDestinationPostalCode: description: > The actual postal code where the PPM shipment ended. To be filled once @@ -2726,17 +2739,13 @@ definitions: destinationType: $ref: '#/definitions/DestinationType' secondaryPickupAddress: - description: >- - A second pickup address for this shipment, if the customer entered - one. An optional field. - allOf: - - $ref: '#/definitions/Address' + $ref: '#/definitions/Address' secondaryDeliveryAddress: - description: >- - A second delivery address for this shipment, if the customer entered - one. An optional field. - allOf: - - $ref: '#/definitions/Address' + $ref: '#/definitions/Address' + tertiaryPickupAddress: + $ref: '#/definitions/Address' + tertiaryDeliveryAddress: + $ref: '#/definitions/Address' storageFacility: allOf: - x-nullable: true