diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 51113c85e01..61bd52e4f5c 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -1911,7 +1911,7 @@ func init() { }, "/move_task_orders/{moveTaskOrderID}/mto_shipments/{shipmentID}": { "patch": { - "description": "Updates a specified MTO shipment.\nRequired fields include:\n* MTO Shipment ID required in path\n* If-Match required in headers\n* No fields required in body\nOptional fields include:\n* New shipment status type\n* Shipment Type\n* Customer requested pick-up date\n* Pick-up Address\n* Delivery Address\n* Secondary Pick-up Address\n* SecondaryDelivery Address\n* Delivery Address Type\n* Customer Remarks\n* Counselor Remarks\n* Releasing / Receiving agents\n* Actual Pro Gear Weight\n* Actual Spouse Pro Gear Weight\n", + "description": "Updates a specified MTO shipment.\nRequired fields include:\n* MTO Shipment ID required in path\n* If-Match required in headers\n* No fields required in body\nOptional fields include:\n* New shipment status type\n* Shipment Type\n* Customer requested pick-up date\n* Pick-up Address\n* Delivery Address\n* Secondary Pick-up Address\n* SecondaryDelivery Address\n* Delivery Address Type\n* Customer Remarks\n* Counselor Remarks\n* Releasing / Receiving agents\n* Actual Pro Gear Weight\n* Actual Spouse Pro Gear Weight\n* Location of the POE/POD\n", "consumes": [ "application/json" ], @@ -9986,6 +9986,12 @@ func init() { "x-nullable": true, "$ref": "#/definitions/Address" }, + "podLocation": { + "$ref": "#/definitions/Port" + }, + "poeLocation": { + "$ref": "#/definitions/Port" + }, "ppmShipment": { "$ref": "#/definitions/PPMShipment" }, @@ -12569,6 +12575,115 @@ func init() { "$ref": "#/definitions/PaymentServiceItem" } }, + "Port": { + "description": "A port that is used to move an international shipment.", + "type": "object", + "properties": { + "city": { + "type": "string", + "example": "PORTLAND" + }, + "country": { + "description": "Two-letter country code", + "type": "string", + "pattern": "^[A-Z]{2}$", + "example": "US" + }, + "county": { + "type": "string", + "example": "MULTNOMAH" + }, + "id": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + }, + "portCode": { + "description": "3 or 4 digit port code", + "type": "string", + "example": "0431" + }, + "portName": { + "description": "Name of the port", + "type": "string", + "example": "PORTLAND INTL" + }, + "portType": { + "description": "Port type A (Air), B (Border Crossing), S (Sea)", + "type": "string", + "enum": [ + "A", + "B", + "S" + ] + }, + "state": { + "description": "US state", + "type": "string", + "enum": [ + "AL", + "AK", + "AR", + "AZ", + "CA", + "CO", + "CT", + "DC", + "DE", + "FL", + "GA", + "HI", + "IA", + "ID", + "IL", + "IN", + "KS", + "KY", + "LA", + "MA", + "MD", + "ME", + "MI", + "MN", + "MO", + "MS", + "MT", + "NC", + "ND", + "NE", + "NH", + "NJ", + "NM", + "NV", + "NY", + "OH", + "OK", + "OR", + "PA", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VA", + "VT", + "WA", + "WI", + "WV", + "WY" + ], + "example": "OR" + }, + "zip": { + "type": "string", + "format": "zip", + "title": "ZIP", + "pattern": "^(\\d{5}([\\-]\\d{4})?)$", + "example": "99501" + } + } + }, "PostDocumentPayload": { "type": "object", "properties": { @@ -17965,7 +18080,7 @@ func init() { }, "/move_task_orders/{moveTaskOrderID}/mto_shipments/{shipmentID}": { "patch": { - "description": "Updates a specified MTO shipment.\nRequired fields include:\n* MTO Shipment ID required in path\n* If-Match required in headers\n* No fields required in body\nOptional fields include:\n* New shipment status type\n* Shipment Type\n* Customer requested pick-up date\n* Pick-up Address\n* Delivery Address\n* Secondary Pick-up Address\n* SecondaryDelivery Address\n* Delivery Address Type\n* Customer Remarks\n* Counselor Remarks\n* Releasing / Receiving agents\n* Actual Pro Gear Weight\n* Actual Spouse Pro Gear Weight\n", + "description": "Updates a specified MTO shipment.\nRequired fields include:\n* MTO Shipment ID required in path\n* If-Match required in headers\n* No fields required in body\nOptional fields include:\n* New shipment status type\n* Shipment Type\n* Customer requested pick-up date\n* Pick-up Address\n* Delivery Address\n* Secondary Pick-up Address\n* SecondaryDelivery Address\n* Delivery Address Type\n* Customer Remarks\n* Counselor Remarks\n* Releasing / Receiving agents\n* Actual Pro Gear Weight\n* Actual Spouse Pro Gear Weight\n* Location of the POE/POD\n", "consumes": [ "application/json" ], @@ -27077,6 +27192,12 @@ func init() { "x-nullable": true, "$ref": "#/definitions/Address" }, + "podLocation": { + "$ref": "#/definitions/Port" + }, + "poeLocation": { + "$ref": "#/definitions/Port" + }, "ppmShipment": { "$ref": "#/definitions/PPMShipment" }, @@ -29734,6 +29855,115 @@ func init() { "$ref": "#/definitions/PaymentServiceItem" } }, + "Port": { + "description": "A port that is used to move an international shipment.", + "type": "object", + "properties": { + "city": { + "type": "string", + "example": "PORTLAND" + }, + "country": { + "description": "Two-letter country code", + "type": "string", + "pattern": "^[A-Z]{2}$", + "example": "US" + }, + "county": { + "type": "string", + "example": "MULTNOMAH" + }, + "id": { + "type": "string", + "format": "uuid", + "example": "c56a4180-65aa-42ec-a945-5fd21dec0538" + }, + "portCode": { + "description": "3 or 4 digit port code", + "type": "string", + "example": "0431" + }, + "portName": { + "description": "Name of the port", + "type": "string", + "example": "PORTLAND INTL" + }, + "portType": { + "description": "Port type A (Air), B (Border Crossing), S (Sea)", + "type": "string", + "enum": [ + "A", + "B", + "S" + ] + }, + "state": { + "description": "US state", + "type": "string", + "enum": [ + "AL", + "AK", + "AR", + "AZ", + "CA", + "CO", + "CT", + "DC", + "DE", + "FL", + "GA", + "HI", + "IA", + "ID", + "IL", + "IN", + "KS", + "KY", + "LA", + "MA", + "MD", + "ME", + "MI", + "MN", + "MO", + "MS", + "MT", + "NC", + "ND", + "NE", + "NH", + "NJ", + "NM", + "NV", + "NY", + "OH", + "OK", + "OR", + "PA", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VA", + "VT", + "WA", + "WI", + "WV", + "WY" + ], + "example": "OR" + }, + "zip": { + "type": "string", + "format": "zip", + "title": "ZIP", + "pattern": "^(\\d{5}([\\-]\\d{4})?)$", + "example": "99501" + } + } + }, "PostDocumentPayload": { "type": "object", "properties": { diff --git a/pkg/gen/ghcapi/ghcoperations/mto_shipment/update_m_t_o_shipment.go b/pkg/gen/ghcapi/ghcoperations/mto_shipment/update_m_t_o_shipment.go index eb0db5e29ab..2ad855d4d8b 100644 --- a/pkg/gen/ghcapi/ghcoperations/mto_shipment/update_m_t_o_shipment.go +++ b/pkg/gen/ghcapi/ghcoperations/mto_shipment/update_m_t_o_shipment.go @@ -53,6 +53,7 @@ Optional fields include: * Releasing / Receiving agents * Actual Pro Gear Weight * Actual Spouse Pro Gear Weight +* Location of the POE/POD */ type UpdateMTOShipment struct { Context *middleware.Context diff --git a/pkg/gen/ghcmessages/m_t_o_shipment.go b/pkg/gen/ghcmessages/m_t_o_shipment.go index 78a6420cee4..793859afdfc 100644 --- a/pkg/gen/ghcmessages/m_t_o_shipment.go +++ b/pkg/gen/ghcmessages/m_t_o_shipment.go @@ -151,6 +151,12 @@ type MTOShipment struct { // pickup address PickupAddress *Address `json:"pickupAddress,omitempty"` + // pod location + PodLocation *Port `json:"podLocation,omitempty"` + + // poe location + PoeLocation *Port `json:"poeLocation,omitempty"` + // ppm shipment PpmShipment *PPMShipment `json:"ppmShipment,omitempty"` @@ -318,6 +324,14 @@ func (m *MTOShipment) Validate(formats strfmt.Registry) error { res = append(res, err) } + if err := m.validatePodLocation(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePoeLocation(formats); err != nil { + res = append(res, err) + } + if err := m.validatePpmShipment(formats); err != nil { res = append(res, err) } @@ -698,6 +712,44 @@ func (m *MTOShipment) validatePickupAddress(formats strfmt.Registry) error { return nil } +func (m *MTOShipment) validatePodLocation(formats strfmt.Registry) error { + if swag.IsZero(m.PodLocation) { // not required + return nil + } + + if m.PodLocation != nil { + if err := m.PodLocation.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("podLocation") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("podLocation") + } + return err + } + } + + return nil +} + +func (m *MTOShipment) validatePoeLocation(formats strfmt.Registry) error { + if swag.IsZero(m.PoeLocation) { // not required + return nil + } + + if m.PoeLocation != nil { + if err := m.PoeLocation.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("poeLocation") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("poeLocation") + } + return err + } + } + + return nil +} + func (m *MTOShipment) validatePpmShipment(formats strfmt.Registry) error { if swag.IsZero(m.PpmShipment) { // not required return nil @@ -1051,6 +1103,14 @@ func (m *MTOShipment) ContextValidate(ctx context.Context, formats strfmt.Regist res = append(res, err) } + if err := m.contextValidatePodLocation(ctx, formats); err != nil { + res = append(res, err) + } + + if err := m.contextValidatePoeLocation(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidatePpmShipment(ctx, formats); err != nil { res = append(res, err) } @@ -1276,6 +1336,48 @@ func (m *MTOShipment) contextValidatePickupAddress(ctx context.Context, formats return nil } +func (m *MTOShipment) contextValidatePodLocation(ctx context.Context, formats strfmt.Registry) error { + + if m.PodLocation != nil { + + if swag.IsZero(m.PodLocation) { // not required + return nil + } + + if err := m.PodLocation.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("podLocation") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("podLocation") + } + return err + } + } + + return nil +} + +func (m *MTOShipment) contextValidatePoeLocation(ctx context.Context, formats strfmt.Registry) error { + + if m.PoeLocation != nil { + + if swag.IsZero(m.PoeLocation) { // not required + return nil + } + + if err := m.PoeLocation.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("poeLocation") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("poeLocation") + } + return err + } + } + + return nil +} + func (m *MTOShipment) contextValidatePpmShipment(ctx context.Context, formats strfmt.Registry) error { if m.PpmShipment != nil { diff --git a/pkg/gen/ghcmessages/port.go b/pkg/gen/ghcmessages/port.go new file mode 100644 index 00000000000..5ffb0256db8 --- /dev/null +++ b/pkg/gen/ghcmessages/port.go @@ -0,0 +1,385 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package ghcmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + "encoding/json" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// Port A port that is used to move an international shipment. +// +// swagger:model Port +type Port struct { + + // city + // Example: PORTLAND + City string `json:"city,omitempty"` + + // Two-letter country code + // Example: US + // Pattern: ^[A-Z]{2}$ + Country string `json:"country,omitempty"` + + // county + // Example: MULTNOMAH + County string `json:"county,omitempty"` + + // id + // Example: c56a4180-65aa-42ec-a945-5fd21dec0538 + // Format: uuid + ID strfmt.UUID `json:"id,omitempty"` + + // 3 or 4 digit port code + // Example: 0431 + PortCode string `json:"portCode,omitempty"` + + // Name of the port + // Example: PORTLAND INTL + PortName string `json:"portName,omitempty"` + + // Port type A (Air), B (Border Crossing), S (Sea) + // Enum: [A B S] + PortType string `json:"portType,omitempty"` + + // US state + // Example: OR + // Enum: [AL AK AR AZ CA CO CT DC DE FL GA HI IA ID IL IN KS KY LA MA MD ME MI MN MO MS MT NC ND NE NH NJ NM NV NY OH OK OR PA RI SC SD TN TX UT VA VT WA WI WV WY] + State string `json:"state,omitempty"` + + // ZIP + // Example: 99501 + // Pattern: ^(\d{5}([\-]\d{4})?)$ + Zip string `json:"zip,omitempty"` +} + +// Validate validates this port +func (m *Port) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCountry(formats); err != nil { + res = append(res, err) + } + + if err := m.validateID(formats); err != nil { + res = append(res, err) + } + + if err := m.validatePortType(formats); err != nil { + res = append(res, err) + } + + if err := m.validateState(formats); err != nil { + res = append(res, err) + } + + if err := m.validateZip(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *Port) validateCountry(formats strfmt.Registry) error { + if swag.IsZero(m.Country) { // not required + return nil + } + + if err := validate.Pattern("country", "body", m.Country, `^[A-Z]{2}$`); err != nil { + return err + } + + return nil +} + +func (m *Port) validateID(formats strfmt.Registry) error { + if swag.IsZero(m.ID) { // not required + return nil + } + + if err := validate.FormatOf("id", "body", "uuid", m.ID.String(), formats); err != nil { + return err + } + + return nil +} + +var portTypePortTypePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["A","B","S"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + portTypePortTypePropEnum = append(portTypePortTypePropEnum, v) + } +} + +const ( + + // PortPortTypeA captures enum value "A" + PortPortTypeA string = "A" + + // PortPortTypeB captures enum value "B" + PortPortTypeB string = "B" + + // PortPortTypeS captures enum value "S" + PortPortTypeS string = "S" +) + +// prop value enum +func (m *Port) validatePortTypeEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, portTypePortTypePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *Port) validatePortType(formats strfmt.Registry) error { + if swag.IsZero(m.PortType) { // not required + return nil + } + + // value enum + if err := m.validatePortTypeEnum("portType", "body", m.PortType); err != nil { + return err + } + + return nil +} + +var portTypeStatePropEnum []interface{} + +func init() { + var res []string + if err := json.Unmarshal([]byte(`["AL","AK","AR","AZ","CA","CO","CT","DC","DE","FL","GA","HI","IA","ID","IL","IN","KS","KY","LA","MA","MD","ME","MI","MN","MO","MS","MT","NC","ND","NE","NH","NJ","NM","NV","NY","OH","OK","OR","PA","RI","SC","SD","TN","TX","UT","VA","VT","WA","WI","WV","WY"]`), &res); err != nil { + panic(err) + } + for _, v := range res { + portTypeStatePropEnum = append(portTypeStatePropEnum, v) + } +} + +const ( + + // PortStateAL captures enum value "AL" + PortStateAL string = "AL" + + // PortStateAK captures enum value "AK" + PortStateAK string = "AK" + + // PortStateAR captures enum value "AR" + PortStateAR string = "AR" + + // PortStateAZ captures enum value "AZ" + PortStateAZ string = "AZ" + + // PortStateCA captures enum value "CA" + PortStateCA string = "CA" + + // PortStateCO captures enum value "CO" + PortStateCO string = "CO" + + // PortStateCT captures enum value "CT" + PortStateCT string = "CT" + + // PortStateDC captures enum value "DC" + PortStateDC string = "DC" + + // PortStateDE captures enum value "DE" + PortStateDE string = "DE" + + // PortStateFL captures enum value "FL" + PortStateFL string = "FL" + + // PortStateGA captures enum value "GA" + PortStateGA string = "GA" + + // PortStateHI captures enum value "HI" + PortStateHI string = "HI" + + // PortStateIA captures enum value "IA" + PortStateIA string = "IA" + + // PortStateID captures enum value "ID" + PortStateID string = "ID" + + // PortStateIL captures enum value "IL" + PortStateIL string = "IL" + + // PortStateIN captures enum value "IN" + PortStateIN string = "IN" + + // PortStateKS captures enum value "KS" + PortStateKS string = "KS" + + // PortStateKY captures enum value "KY" + PortStateKY string = "KY" + + // PortStateLA captures enum value "LA" + PortStateLA string = "LA" + + // PortStateMA captures enum value "MA" + PortStateMA string = "MA" + + // PortStateMD captures enum value "MD" + PortStateMD string = "MD" + + // PortStateME captures enum value "ME" + PortStateME string = "ME" + + // PortStateMI captures enum value "MI" + PortStateMI string = "MI" + + // PortStateMN captures enum value "MN" + PortStateMN string = "MN" + + // PortStateMO captures enum value "MO" + PortStateMO string = "MO" + + // PortStateMS captures enum value "MS" + PortStateMS string = "MS" + + // PortStateMT captures enum value "MT" + PortStateMT string = "MT" + + // PortStateNC captures enum value "NC" + PortStateNC string = "NC" + + // PortStateND captures enum value "ND" + PortStateND string = "ND" + + // PortStateNE captures enum value "NE" + PortStateNE string = "NE" + + // PortStateNH captures enum value "NH" + PortStateNH string = "NH" + + // PortStateNJ captures enum value "NJ" + PortStateNJ string = "NJ" + + // PortStateNM captures enum value "NM" + PortStateNM string = "NM" + + // PortStateNV captures enum value "NV" + PortStateNV string = "NV" + + // PortStateNY captures enum value "NY" + PortStateNY string = "NY" + + // PortStateOH captures enum value "OH" + PortStateOH string = "OH" + + // PortStateOK captures enum value "OK" + PortStateOK string = "OK" + + // PortStateOR captures enum value "OR" + PortStateOR string = "OR" + + // PortStatePA captures enum value "PA" + PortStatePA string = "PA" + + // PortStateRI captures enum value "RI" + PortStateRI string = "RI" + + // PortStateSC captures enum value "SC" + PortStateSC string = "SC" + + // PortStateSD captures enum value "SD" + PortStateSD string = "SD" + + // PortStateTN captures enum value "TN" + PortStateTN string = "TN" + + // PortStateTX captures enum value "TX" + PortStateTX string = "TX" + + // PortStateUT captures enum value "UT" + PortStateUT string = "UT" + + // PortStateVA captures enum value "VA" + PortStateVA string = "VA" + + // PortStateVT captures enum value "VT" + PortStateVT string = "VT" + + // PortStateWA captures enum value "WA" + PortStateWA string = "WA" + + // PortStateWI captures enum value "WI" + PortStateWI string = "WI" + + // PortStateWV captures enum value "WV" + PortStateWV string = "WV" + + // PortStateWY captures enum value "WY" + PortStateWY string = "WY" +) + +// prop value enum +func (m *Port) validateStateEnum(path, location string, value string) error { + if err := validate.EnumCase(path, location, value, portTypeStatePropEnum, true); err != nil { + return err + } + return nil +} + +func (m *Port) validateState(formats strfmt.Registry) error { + if swag.IsZero(m.State) { // not required + return nil + } + + // value enum + if err := m.validateStateEnum("state", "body", m.State); err != nil { + return err + } + + return nil +} + +func (m *Port) validateZip(formats strfmt.Registry) error { + if swag.IsZero(m.Zip) { // not required + return nil + } + + if err := validate.Pattern("zip", "body", m.Zip, `^(\d{5}([\-]\d{4})?)$`); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this port based on context it is used +func (m *Port) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *Port) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *Port) UnmarshalBinary(b []byte) error { + var res Port + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go index 20be502ac48..e2c02f30c06 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload.go @@ -1523,6 +1523,8 @@ func MTOShipment(storer storage.FileStorer, mtoShipment *models.MTOShipment, sit DeliveryAddressUpdate: ShipmentAddressUpdate(mtoShipment.DeliveryAddressUpdate), ShipmentLocator: handlers.FmtStringPtr(mtoShipment.ShipmentLocator), MarketCode: MarketCode(&mtoShipment.MarketCode), + PoeLocation: Port(mtoShipment.MTOServiceItems, "POE"), + PodLocation: Port(mtoShipment.MTOServiceItems, "POD"), } if mtoShipment.Distance != nil { @@ -2734,3 +2736,33 @@ func VLocations(vLocations models.VLocations) ghcmessages.VLocations { } return payload } + +func Port(mtoServiceItems models.MTOServiceItems, portType string) *ghcmessages.Port { + if mtoServiceItems == nil { + return nil + } + + for _, mtoServiceItem := range mtoServiceItems { + var portLocation *models.PortLocation + if portType == "POE" && mtoServiceItem.POELocation != nil { + portLocation = mtoServiceItem.POELocation + } else if portType == "POD" && mtoServiceItem.PODLocation != nil { + portLocation = mtoServiceItem.PODLocation + } + + if portLocation != nil { + return &ghcmessages.Port{ + ID: strfmt.UUID(portLocation.ID.String()), + PortType: portLocation.Port.PortType.String(), + PortCode: portLocation.Port.PortCode, + PortName: portLocation.Port.PortName, + City: portLocation.City.CityName, + County: portLocation.UsPostRegionCity.UsprcCountyNm, + State: portLocation.UsPostRegionCity.UsPostRegion.State.StateName, + Zip: portLocation.UsPostRegionCity.UsprZipID, + Country: portLocation.Country.CountryName, + } + } + } + return nil +} diff --git a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go index e170710f29f..fe164e85723 100644 --- a/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go +++ b/pkg/handlers/ghcapi/internal/payloads/model_to_payload_test.go @@ -905,16 +905,16 @@ func (suite *PayloadsSuite) TestReServiceItems() { marketCodeInternational := models.MarketCodeInternational marketCodeDomestic := models.MarketCodeDomestic poefscReServiceCode := models.ReServiceCodePOEFSC - poedscReServiceCode := models.ReServiceCodePODFSC + podfscReServiceCode := models.ReServiceCodePODFSC poefscServiceName := "International POE Fuel Surcharge" - poedscServiceName := "International POD Fuel Surcharge" + podfscServiceName := "International POD Fuel Surcharge" poefscService := models.ReService{ Code: poefscReServiceCode, Name: poefscServiceName, } podfscService := models.ReService{ - Code: poedscReServiceCode, - Name: poedscServiceName, + Code: podfscReServiceCode, + Name: podfscServiceName, } hhgShipmentType := models.MTOShipmentTypeHHG ubShipmentType := models.MTOShipmentTypeUnaccompaniedBaggage @@ -1218,3 +1218,138 @@ func (suite *PayloadsSuite) TestMTOServiceItemModel() { suite.Equal(handlers.FmtString(models.MarketOconus.FullString()), result.Market, "Expected Market to be OCONUS") }) } + +func (suite *PayloadsSuite) TestPort() { + + suite.Run("returns nil when PortLocation is nil", func() { + var mtoServiceItems models.MTOServiceItems = nil + result := Port(mtoServiceItems, "POE") + suite.Nil(result, "Expected result to be nil when Port Location is nil") + }) + + suite.Run("Success - Maps PortLocation to Port payload", func() { + // Use the factory to create a port location + portLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "PDX", + }, + }, + }, nil) + + mtoServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodePOEFSC, + }, + }, + { + Model: portLocation, + LinkOnly: true, + Type: &factory.PortLocations.PortOfEmbarkation, + }, + }, nil) + + // Actual + mtoServiceItems := models.MTOServiceItems{mtoServiceItem} + result := Port(mtoServiceItems, "POE") + + // Assert + suite.IsType(&ghcmessages.Port{}, result) + suite.Equal(strfmt.UUID(portLocation.ID.String()), result.ID) + suite.Equal(portLocation.Port.PortType.String(), result.PortType) + suite.Equal(portLocation.Port.PortCode, result.PortCode) + suite.Equal(portLocation.Port.PortName, result.PortName) + suite.Equal(portLocation.City.CityName, result.City) + suite.Equal(portLocation.UsPostRegionCity.UsprcCountyNm, result.County) + suite.Equal(portLocation.UsPostRegionCity.UsPostRegion.State.StateName, result.State) + suite.Equal(portLocation.UsPostRegionCity.UsprZipID, result.Zip) + suite.Equal(portLocation.Country.CountryName, result.Country) + }) +} + +func (suite *PayloadsSuite) TestMTOShipment_POE_POD_Locations() { + suite.Run("Only POE Location is set", func() { + // Create mock data for MTOServiceItems with POE and POD + poePortLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "PDX", + }, + }, + }, nil) + + poefscServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodePOEFSC, + Priority: 1, + }, + }, + { + Model: poePortLocation, + LinkOnly: true, + Type: &factory.PortLocations.PortOfEmbarkation, + }, + }, nil) + + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MTOServiceItems: models.MTOServiceItems{poefscServiceItem}, + }, + }, + }, nil) + + payload := MTOShipment(nil, &mtoShipment, nil) + + // Assertions + suite.NotNil(payload, "Expected payload to not be nil") + suite.NotNil(payload.PoeLocation, "Expected POELocation to not be nil") + suite.Equal("PDX", payload.PoeLocation.PortCode, "Expected POE Port Code to match") + suite.Equal("PORTLAND INTL", payload.PoeLocation.PortName, "Expected POE Port Name to match") + suite.Nil(payload.PodLocation, "Expected PODLocation to be nil when POELocation is set") + }) + + suite.Run("Only POD Location is set", func() { + // Create mock data for MTOServiceItems with POE and POD + podPortLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{ + { + Model: models.Port{ + PortCode: "PDX", + }, + }, + }, nil) + + podfscServiceItem := factory.BuildMTOServiceItem(nil, []factory.Customization{ + { + Model: models.ReService{ + Code: models.ReServiceCodePODFSC, + Priority: 1, + }, + }, + { + Model: podPortLocation, + LinkOnly: true, + Type: &factory.PortLocations.PortOfDebarkation, + }, + }, nil) + + mtoShipment := factory.BuildMTOShipment(suite.DB(), []factory.Customization{ + { + Model: models.MTOShipment{ + MTOServiceItems: models.MTOServiceItems{podfscServiceItem}, + }, + }, + }, nil) + + payload := MTOShipment(nil, &mtoShipment, nil) + + // Assertions + suite.NotNil(payload, "Expected payload to not be nil") + suite.NotNil(payload.PodLocation, "Expected PODLocation to not be nil") + suite.Equal("PDX", payload.PodLocation.PortCode, "Expected POD Port Code to match") + suite.Equal("PORTLAND INTL", payload.PodLocation.PortName, "Expected POD Port Name to match") + suite.Nil(payload.PoeLocation, "Expected PODLocation to be nil when PODLocation is set") + }) +} diff --git a/pkg/services/mto_shipment/mto_shipment_fetcher.go b/pkg/services/mto_shipment/mto_shipment_fetcher.go index 6796fec5b30..01a19c9fbdb 100644 --- a/pkg/services/mto_shipment/mto_shipment_fetcher.go +++ b/pkg/services/mto_shipment/mto_shipment_fetcher.go @@ -53,6 +53,8 @@ func (f mtoShipmentFetcher) ListMTOShipments(appCtx appcontext.AppContext, moveI "SecondaryDeliveryAddress.Country", "TertiaryDeliveryAddress.Country", "MTOServiceItems.Dimensions", + "MTOServiceItems.PODLocation.Port", + "MTOServiceItems.POELocation.Port", "BoatShipment", "MobileHome", "PPMShipment.W2Address", @@ -146,6 +148,22 @@ func (f mtoShipmentFetcher) ListMTOShipments(appCtx appcontext.AppContext, moveI return nil, err } shipments[i].MTOAgents = agents + + //Pull the port location info back + for _, serviceItem := range shipments[i].MTOServiceItems { + if serviceItem.PODLocation != nil { + loadErr := appCtx.DB().Load(serviceItem.PODLocation, "City", "Country", "UsPostRegionCity.UsPostRegion.State") + if loadErr != nil { + return nil, loadErr + } + } + if serviceItem.POELocation != nil { + loadErr := appCtx.DB().Load(serviceItem.POELocation, "City", "Country", "UsPostRegionCity.UsPostRegion.State") + if loadErr != nil { + return nil, loadErr + } + } + } } return shipments, nil diff --git a/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx b/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx index 34b8f0c791d..876aca3a848 100644 --- a/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx +++ b/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Button } from '@trussworks/react-uswds'; import { AddressShape } from '../../../types/address'; -import { formatAddress, formatCityStateAndPostalCode } from '../../../utils/shipmentDisplay'; +import { formatAddress, formatCityStateAndPostalCode, formatPortInfo } from '../../../utils/shipmentDisplay'; import DataTableWrapper from '../../DataTableWrapper/index'; import DataTable from '../../DataTable/index'; @@ -25,6 +25,8 @@ const ShipmentAddresses = ({ handleShowDiversionModal, shipmentInfo, isMoveLocked, + poeLocation, + podLocation, }) => { let pickupHeader; let destinationHeader; @@ -85,6 +87,10 @@ const ShipmentAddresses = ({ icon={} data-testid="pickupDestinationAddress" /> + ); }; diff --git a/src/components/Office/ShipmentAddresses/ShipmentAddresses.test.jsx b/src/components/Office/ShipmentAddresses/ShipmentAddresses.test.jsx index 9153061aafa..9521d67fbbb 100644 --- a/src/components/Office/ShipmentAddresses/ShipmentAddresses.test.jsx +++ b/src/components/Office/ShipmentAddresses/ShipmentAddresses.test.jsx @@ -42,6 +42,8 @@ const testProps = { shipmentLocator: 'ABCDEF-01', }, diversionReason: '', + poeLocation: null, + podLocation: null, }; const ppmShipment = { @@ -184,6 +186,8 @@ describe('ShipmentAddresses', () => { it('shows correct headings for HHG', () => { render(); expect(screen.getByText("Customer's addresses")).toBeInTheDocument(); + expect(screen.getByText('Port of Embarkation')).toBeInTheDocument(); + expect(screen.getByText('Port of Debarkation')).toBeInTheDocument(); }); it('shows correct headings for NTS', () => { diff --git a/src/components/Office/ShipmentDetails/ShipmentDetailsMain.jsx b/src/components/Office/ShipmentDetails/ShipmentDetailsMain.jsx index 430d0808f60..c1914d2f9c1 100644 --- a/src/components/Office/ShipmentDetails/ShipmentDetailsMain.jsx +++ b/src/components/Office/ShipmentDetails/ShipmentDetailsMain.jsx @@ -67,6 +67,8 @@ const ShipmentDetailsMain = ({ storageInTransit, shipmentType, storageFacility, + poeLocation, + podLocation, } = shipment; const { originDutyLocationAddress, destinationDutyLocationAddress } = dutyLocationAddresses; @@ -135,6 +137,8 @@ const ShipmentDetailsMain = ({ let pickupActualDate; let plannedMoveDate; let actualMoveDate; + let displayPoeLocation; + let displayPodLocation; switch (shipmentType) { case SHIPMENT_OPTIONS.HHG: @@ -144,6 +148,8 @@ const ShipmentDetailsMain = ({ weightResult = primeEstimatedWeight; displayedPickupAddress = pickupAddress; displayedDeliveryAddress = destinationAddress || destinationDutyLocationAddress; + displayPoeLocation = poeLocation; + displayPodLocation = podLocation; break; case SHIPMENT_OPTIONS.NTS: pickupRequestedDate = requestedPickupDate; @@ -175,6 +181,8 @@ const ShipmentDetailsMain = ({ weightResult = primeEstimatedWeight; displayedPickupAddress = pickupAddress; displayedDeliveryAddress = destinationAddress || destinationDutyLocationAddress; + displayPoeLocation = poeLocation; + displayPodLocation = podLocation; break; } @@ -260,6 +268,8 @@ const ShipmentDetailsMain = ({ }} handleShowDiversionModal={handleShowDiversionModal} isMoveLocked={isMoveLocked} + poeLocation={displayPoeLocation} + podLocation={displayPodLocation} /> { expect(shipmentType).toEqual(shipmentModificationTypes.DIVERSION); }); }); + + describe('formatPortInfo', () => { + it('formats port information correctly when all fields are provided', () => { + const port = { + portCode: 'PDX', + portName: 'PORTLAND INTL', + city: 'PORTLAND', + state: 'OREGON', + zip: '97220', + }; + const formattedPortInfo = formatPortInfo(port); + expect(formattedPortInfo).toEqual('PDX - PORTLAND INTL\nPORTLAND, OREGON 97220'); + }); + + it('returns a dash when no port is provided', () => { + const formattedPortInfo = formatPortInfo(null); + expect(formattedPortInfo).toEqual('-'); + }); + }); }); diff --git a/swagger-def/definitions/MTOShipment.yaml b/swagger-def/definitions/MTOShipment.yaml index 2e3f55c6fbc..d11a9ec4023 100644 --- a/swagger-def/definitions/MTOShipment.yaml +++ b/swagger-def/definitions/MTOShipment.yaml @@ -217,3 +217,7 @@ properties: - 'i' example: 'd' description: 'Single-letter designator for domestic (d) or international (i) shipments' + podLocation: + $ref: 'Port.yaml' + poeLocation: + $ref: 'Port.yaml' diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 01d005b1ae6..e95349bcc3c 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -1062,6 +1062,7 @@ paths: * Releasing / Receiving agents * Actual Pro Gear Weight * Actual Spouse Pro Gear Weight + * Location of the POE/POD consumes: - application/json produces: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index d8247650b54..23c28fdb556 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -1113,6 +1113,7 @@ paths: * Releasing / Receiving agents * Actual Pro Gear Weight * Actual Spouse Pro Gear Weight + * Location of the POE/POD consumes: - application/json produces: @@ -10628,6 +10629,102 @@ definitions: - originalAddress - newAddress - contractorRemarks + Port: + description: A port that is used to move an international shipment. + type: object + properties: + id: + type: string + format: uuid + example: c56a4180-65aa-42ec-a945-5fd21dec0538 + portType: + type: string + description: Port type A (Air), B (Border Crossing), S (Sea) + enum: + - A + - B + - S + portCode: + type: string + description: 3 or 4 digit port code + example: '0431' + portName: + type: string + description: Name of the port + example: PORTLAND INTL + city: + type: string + example: PORTLAND + county: + type: string + example: MULTNOMAH + state: + type: string + description: US state + example: OR + enum: + - AL + - AK + - AR + - AZ + - CA + - CO + - CT + - DC + - DE + - FL + - GA + - HI + - IA + - ID + - IL + - IN + - KS + - KY + - LA + - MA + - MD + - ME + - MI + - MN + - MO + - MS + - MT + - NC + - ND + - NE + - NH + - NJ + - NM + - NV + - NY + - OH + - OK + - OR + - PA + - RI + - SC + - SD + - TN + - TX + - UT + - VA + - VT + - WA + - WI + - WV + - WY + zip: + type: string + format: zip + title: ZIP + example: '99501' + pattern: ^(\d{5}([\-]\d{4})?)$ + country: + type: string + example: US + pattern: ^[A-Z]{2}$ + description: Two-letter country code MTOShipment: properties: moveTaskOrderID: @@ -10858,6 +10955,10 @@ definitions: description: >- Single-letter designator for domestic (d) or international (i) shipments + podLocation: + $ref: '#/definitions/Port' + poeLocation: + $ref: '#/definitions/Port' LOATypeNullable: description: The Line of accounting (TAC/SAC) type that will be used for the shipment type: string