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/handlers/ghcapi/queues.go b/pkg/handlers/ghcapi/queues.go
index e132e7942ba..ab48a061fc1 100644
--- a/pkg/handlers/ghcapi/queues.go
+++ b/pkg/handlers/ghcapi/queues.go
@@ -737,6 +737,27 @@ func (h GetBulkAssignmentDataHandler) Handle(
return queues.NewGetBulkAssignmentDataInternalServerError(), err
}
+ officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID)
+ case string(models.QueueTypeTaskOrder):
+ // fetch the TOOs who work at their office
+ officeUsers, err := h.OfficeUserFetcherPop.FetchOfficeUsersWithWorkloadByRoleAndOffice(
+ appCtx,
+ roles.RoleTypeTOO,
+ officeUser.TransportationOfficeID,
+ )
+ if err != nil {
+ appCtx.Logger().Error("Error retreiving office users", zap.Error(err))
+ return queues.NewGetBulkAssignmentDataInternalServerError(), err
+ }
+ // fetch the moves available to be assigned to their office users
+ moves, err := h.MoveFetcherBulkAssignment.FetchMovesForBulkAssignmentTaskOrder(
+ appCtx, officeUser.TransportationOffice.Gbloc, officeUser.TransportationOffice.ID,
+ )
+ if err != nil {
+ appCtx.Logger().Error("Error retreiving moves", zap.Error(err))
+ return queues.NewGetBulkAssignmentDataInternalServerError(), err
+ }
+
officeUserData = payloads.BulkAssignmentData(appCtx, moves, officeUsers, officeUser.TransportationOffice.ID)
}
return queues.NewGetBulkAssignmentDataOK().WithPayload(&officeUserData), nil
diff --git a/pkg/handlers/ghcapi/queues_test.go b/pkg/handlers/ghcapi/queues_test.go
index 631ccb1e52a..e0fb5a4007f 100644
--- a/pkg/handlers/ghcapi/queues_test.go
+++ b/pkg/handlers/ghcapi/queues_test.go
@@ -1884,4 +1884,69 @@ func (suite *HandlerSuite) TestGetBulkAssignmentDataHandler() {
suite.Len(payload.AvailableOfficeUsers, 1)
suite.Len(payload.BulkAssignmentMoveIDs, 1)
})
+ suite.Run("TOO: returns properly formatted bulk assignment data", func() {
+ transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil)
+
+ officeUser := factory.BuildOfficeUserWithPrivileges(suite.DB(), []factory.Customization{
+ {
+ Model: models.OfficeUser{
+ Email: "officeuser1@example.com",
+ Active: true,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ {
+ Model: models.User{
+ Privileges: []models.Privilege{
+ {
+ PrivilegeType: models.PrivilegeTypeSupervisor,
+ },
+ },
+ Roles: []roles.Role{
+ {
+ RoleType: roles.RoleTypeTOO,
+ },
+ },
+ },
+ },
+ }, nil)
+
+ // move to appear in the return
+ factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusAPPROVALSREQUESTED,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+
+ request := httptest.NewRequest("GET", "/queues/bulk-assignment", nil)
+ request = suite.AuthenticateOfficeRequest(request, officeUser)
+ params := queues.GetBulkAssignmentDataParams{
+ HTTPRequest: request,
+ QueueType: models.StringPointer("TASK_ORDER"),
+ }
+ handlerConfig := suite.HandlerConfig()
+ handler := GetBulkAssignmentDataHandler{
+ handlerConfig,
+ officeusercreator.NewOfficeUserFetcherPop(),
+ movefetcher.NewMoveFetcherBulkAssignment(),
+ }
+ response := handler.Handle(params)
+ suite.IsNotErrResponse(response)
+ suite.IsType(&queues.GetBulkAssignmentDataOK{}, response)
+ payload := response.(*queues.GetBulkAssignmentDataOK).Payload
+ suite.NoError(payload.Validate(strfmt.Default))
+ suite.Len(payload.AvailableOfficeUsers, 1)
+ suite.Len(payload.BulkAssignmentMoveIDs, 1)
+ })
}
diff --git a/pkg/services/move.go b/pkg/services/move.go
index 237e360fe3f..8ddda83a8ec 100644
--- a/pkg/services/move.go
+++ b/pkg/services/move.go
@@ -31,6 +31,7 @@ type MoveFetcher interface {
type MoveFetcherBulkAssignment interface {
FetchMovesForBulkAssignmentCounseling(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error)
+ FetchMovesForBulkAssignmentTaskOrder(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error)
}
//go:generate mockery --name MoveSearcher
diff --git a/pkg/services/move/move_fetcher.go b/pkg/services/move/move_fetcher.go
index f4fc42dea57..a1ec1d8bbed 100644
--- a/pkg/services/move/move_fetcher.go
+++ b/pkg/services/move/move_fetcher.go
@@ -165,3 +165,50 @@ func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentCounseling(appCtx
return moves, nil
}
+
+func (f moveFetcherBulkAssignment) FetchMovesForBulkAssignmentTaskOrder(appCtx appcontext.AppContext, gbloc string, officeId uuid.UUID) ([]models.MoveWithEarliestDate, error) {
+ var moves []models.MoveWithEarliestDate
+
+ err := appCtx.DB().
+ RawQuery(`SELECT
+ moves.id,
+ MIN(LEAST(
+ COALESCE(mto_shipments.requested_pickup_date, '9999-12-31'),
+ COALESCE(mto_shipments.requested_delivery_date, '9999-12-31'),
+ COALESCE(ppm_shipments.expected_departure_date, '9999-12-31')
+ )) AS earliest_date
+ FROM moves
+ INNER JOIN orders ON orders.id = moves.orders_id
+ INNER JOIN service_members ON orders.service_member_id = service_members.id
+ INNER JOIN mto_shipments ON mto_shipments.move_id = moves.id
+ LEFT JOIN ppm_shipments ON ppm_shipments.shipment_id = mto_shipments.id
+ LEFT JOIN move_to_gbloc ON move_to_gbloc.move_id = moves.id
+ WHERE
+ (moves.status IN ('APPROVALS REQUESTED', 'SUBMITTED', 'SERVICE COUNSELING COMPLETED'))
+ AND moves.show = $1
+ AND moves.too_assigned_id IS NULL
+ AND (orders.orders_type NOT IN ($2, $3, $4))
+ AND service_members.affiliation != 'MARINES'
+ AND ((mto_shipments.shipment_type != $5 AND move_to_gbloc.gbloc = $6) OR (mto_shipments.shipment_type = $7 AND orders.gbloc = $8))
+ GROUP BY moves.id
+ ORDER BY earliest_date ASC`,
+ models.BoolPointer(true),
+ internalmessages.OrdersTypeBLUEBARK,
+ internalmessages.OrdersTypeWOUNDEDWARRIOR,
+ internalmessages.OrdersTypeSAFETY,
+ models.MTOShipmentTypeHHGOutOfNTSDom,
+ gbloc,
+ models.MTOShipmentTypeHHGOutOfNTSDom,
+ gbloc).
+ All(&moves)
+
+ if err != nil {
+ return nil, fmt.Errorf("error fetching moves for office: %s with error %w", officeId, err)
+ }
+
+ if len(moves) < 1 {
+ return nil, nil
+ }
+
+ return moves, nil
+}
diff --git a/pkg/services/move/move_fetcher_test.go b/pkg/services/move/move_fetcher_test.go
index 381199013cd..92fb23751bb 100644
--- a/pkg/services/move/move_fetcher_test.go
+++ b/pkg/services/move/move_fetcher_test.go
@@ -433,4 +433,65 @@ func (suite *MoveServiceSuite) TestMoveFetcherBulkAssignment() {
// Orders type isn't WW, BB, or Safety
suite.Equal(assignedMove.Orders.OrdersType, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION)
})
+
+ suite.Run("TOO: Returns moves that fulfill the query criteria", func() {
+ moveFetcher := NewMoveFetcherBulkAssignment()
+ transportationOffice := factory.BuildTransportationOffice(suite.DB(), nil, nil)
+ officeUser := factory.BuildOfficeUserWithRoles(suite.DB(), []factory.Customization{
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, []roles.RoleType{roles.RoleTypeTOO})
+
+ factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusAPPROVALSREQUESTED,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+
+ factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusServiceCounselingCompleted,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ }, nil)
+
+ marine := models.AffiliationMARINES
+ factory.BuildMoveWithShipment(suite.DB(), []factory.Customization{
+ {
+ Model: models.Move{
+ Status: models.MoveStatusServiceCounselingCompleted,
+ },
+ },
+ {
+ Model: transportationOffice,
+ LinkOnly: true,
+ Type: &factory.TransportationOffices.CounselingOffice,
+ },
+ {
+ Model: models.ServiceMember{
+ Affiliation: &marine,
+ },
+ },
+ }, nil)
+
+ moves, err := moveFetcher.FetchMovesForBulkAssignmentTaskOrder(suite.AppContextForTest(), "KKFA", officeUser.TransportationOffice.ID)
+ suite.FatalNoError(err)
+ suite.Equal(2, len(moves))
+ })
}
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/pkg/services/mto_shipment/mto_shipment_fetcher_test.go b/pkg/services/mto_shipment/mto_shipment_fetcher_test.go
index 97fc19ca4ea..8a96b1ef739 100644
--- a/pkg/services/mto_shipment/mto_shipment_fetcher_test.go
+++ b/pkg/services/mto_shipment/mto_shipment_fetcher_test.go
@@ -216,7 +216,7 @@ func (suite *MTOShipmentServiceSuite) TestListMTOShipments() {
},
}, []factory.Trait{factory.GetTraitShipmentAddressUpdateRequested})
- serviceItem := testdatagen.MakeMTOServiceItemDomesticCrating(suite.DB(), testdatagen.Assertions{
+ serviceItemDCRT := testdatagen.MakeMTOServiceItemDomesticCrating(suite.DB(), testdatagen.Assertions{
ReService: models.ReService{
Code: models.ReServiceCodeDCRT,
},
@@ -224,6 +224,31 @@ func (suite *MTOShipmentServiceSuite) TestListMTOShipments() {
Move: move,
})
+ portLocation := factory.FetchPortLocation(suite.DB(), []factory.Customization{
+ {
+ Model: models.Port{
+ PortCode: "PDX",
+ },
+ },
+ }, nil)
+
+ serviceItemPortFSC := factory.BuildMTOServiceItem(suite.DB(), []factory.Customization{
+ {
+ Model: models.ReService{
+ Code: models.ReServiceCodePOEFSC,
+ },
+ },
+ {
+ Model: portLocation,
+ LinkOnly: true,
+ Type: &factory.PortLocations.PortOfEmbarkation,
+ },
+ {
+ Model: shipment,
+ LinkOnly: true,
+ },
+ }, nil)
+
agents := factory.BuildMTOAgent(suite.DB(), []factory.Customization{
{
Model: shipment,
@@ -253,7 +278,8 @@ func (suite *MTOShipmentServiceSuite) TestListMTOShipments() {
actualShipment := mtoShipments[0]
- suite.Equal(serviceItem.ReService.Code, actualShipment.MTOServiceItems[0].ReService.Code)
+ suite.Equal(serviceItemDCRT.ReService.Code, actualShipment.MTOServiceItems[0].ReService.Code)
+ suite.Equal(serviceItemPortFSC.ReService.Code, actualShipment.MTOServiceItems[1].ReService.Code)
suite.Equal(agents.ID.String(), actualShipment.MTOAgents[0].ID.String())
suite.Equal(shipment.PickupAddress.ID.String(), actualShipment.PickupAddress.ID.String())
suite.Equal(secondaryPickupAddress.ID.String(), actualShipment.SecondaryPickupAddress.ID.String())
diff --git a/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx b/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx
index 34b8f0c791d..3fe5d3e849e 100644
--- a/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx
+++ b/src/components/Office/ShipmentAddresses/ShipmentAddresses.jsx
@@ -11,6 +11,7 @@ import DataTable from '../../DataTable/index';
import styles from './ShipmentAddresses.module.scss';
+import { formatPortInfo } from 'utils/formatters';
import { shipmentStatuses } from 'constants/shipments';
import { ShipmentOptionsOneOf, ShipmentStatusesOneOf } from 'types/shipment';
import { SHIPMENT_OPTIONS } from 'shared/constants';
@@ -25,6 +26,8 @@ const ShipmentAddresses = ({
handleShowDiversionModal,
shipmentInfo,
isMoveLocked,
+ poeLocation,
+ podLocation,
}) => {
let pickupHeader;
let destinationHeader;
@@ -85,6 +88,12 @@ const ShipmentAddresses = ({
icon={}
data-testid="pickupDestinationAddress"
/>
+ {(poeLocation || podLocation) && (
+
+ )}
);
};
diff --git a/src/components/Office/ShipmentAddresses/ShipmentAddresses.test.jsx b/src/components/Office/ShipmentAddresses/ShipmentAddresses.test.jsx
index 9153061aafa..e99661d1032 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 = {
@@ -117,6 +119,17 @@ const cancellationRequestedShipment = {
},
};
+const shipmentWithPort = {
+ ...testProps,
+ poeLocation: {
+ portCode: 'PDX',
+ portName: 'PORTLAND INTL',
+ city: 'PORTLAND',
+ state: 'OREGON',
+ zip: '97220',
+ },
+};
+
describe('ShipmentAddresses', () => {
it('calls props.handleShowDiversionModal on request diversion button click', async () => {
render(
@@ -233,4 +246,10 @@ describe('ShipmentAddresses', () => {
const requestDiversionBtn = screen.getByRole('button', { name: 'Request Diversion' });
expect(requestDiversionBtn).toBeDisabled();
});
+
+ it('renders port of embarkation and debarkation if one is set', async () => {
+ render();
+ expect(screen.getByText('Port of Embarkation')).toBeInTheDocument();
+ expect(screen.getByText('Port of Debarkation')).toBeInTheDocument();
+ });
});
diff --git a/src/components/Office/ShipmentDetails/ShipmentDetails.stories.jsx b/src/components/Office/ShipmentDetails/ShipmentDetails.stories.jsx
index 107bed9636c..15d814d367a 100644
--- a/src/components/Office/ShipmentDetails/ShipmentDetails.stories.jsx
+++ b/src/components/Office/ShipmentDetails/ShipmentDetails.stories.jsx
@@ -109,6 +109,13 @@ const shipment = {
serviceOrderNumber: '1234',
tacType: LOA_TYPE.HHG,
sacType: LOA_TYPE.NTS,
+ poeLocation: {
+ portCode: 'PDX',
+ portName: 'PORTLAND INTL',
+ city: 'PORTLAND',
+ state: 'OREGON',
+ zip: '97220',
+ },
};
const order = {
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}
/>
{
null,
};
};
+
+/**
+ * @description Converts a string to title case (capitalizes the first letter of each word)
+ * @param {string} str - The input string to format.
+ * @returns {string} - the formatted string in the title case.
+ * */
+export function toTitleCase(str) {
+ if (!str) return '';
+ return str
+ .toLowerCase()
+ .split(' ')
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1))
+ .join(' ');
+}
+
+/**
+ * @description This function is used to format the port in the
+ * ShipmentAddresses component.
+ * It displays only the port code, port name, city, state and zip code.
+ * */
+export function formatPortInfo(port) {
+ if (port) {
+ const formattedCity = toTitleCase(port.city);
+ const formattedState = toTitleCase(port.state);
+ return `${port.portCode} - ${port.portName}\n${formattedCity}, ${formattedState} ${port.zip}`;
+ }
+ return '-';
+}
diff --git a/src/utils/formatters.test.js b/src/utils/formatters.test.js
index 7a9d8135958..387fae0bef7 100644
--- a/src/utils/formatters.test.js
+++ b/src/utils/formatters.test.js
@@ -437,3 +437,54 @@ describe('constructSCOrderOconusFields', () => {
});
});
});
+
+describe('formatPortInfo', () => {
+ it('formats port information correctly when all fields are provided', () => {
+ const values = {
+ portCode: 'PDX',
+ portName: 'PORTLAND INTL',
+ city: 'PORTLAND',
+ state: 'OREGON',
+ zip: '97220',
+ };
+ const result = formatters.formatPortInfo(values);
+ expect(result).toEqual('PDX - PORTLAND INTL\nPortland, Oregon 97220');
+ });
+
+ it('returns a dash when no port is provided', () => {
+ const result = formatters.formatPortInfo(null);
+ expect(result).toEqual('-');
+ });
+});
+
+describe('toTitleCase', () => {
+ it('correctly formats a lowercase string', () => {
+ const values = 'portland oregon';
+ const result = formatters.toTitleCase(values);
+ expect(result).toEqual('Portland Oregon');
+ });
+
+ it('correctly formats an uppercase string', () => {
+ const values = 'PORTLAND OREGON';
+ const result = formatters.toTitleCase(values);
+ expect(result).toEqual('Portland Oregon');
+ });
+
+ it('return an empty string when given an empty string', () => {
+ const values = '';
+ const result = formatters.toTitleCase(values);
+ expect(result).toEqual('');
+ });
+
+ it('return an empty string when given when input is null', () => {
+ const values = null;
+ const result = formatters.toTitleCase(values);
+ expect(result).toEqual('');
+ });
+
+ it('does not alter strings that are already in title case', () => {
+ const values = 'Portland Oregon';
+ const result = formatters.toTitleCase(values);
+ expect(result).toEqual('Portland Oregon');
+ });
+});
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