Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MAIN-B-21509 #13990

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions migrations/app/migrations_manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1020,3 +1020,6 @@
20241007224427_update_addresses_us_post_region_cities_id.up.sql
20241008212243_populate_market_code_on_shipments_table.up.sql
20241009210749_create_view_v_locations.up.sql
20241023184337_create_ports_table.up.sql
20241023184350_create_port_location_table.up.sql
20241023184437_insert_ports.up.sql
21 changes: 21 additions & 0 deletions migrations/app/schema/20241023184337_create_ports_table.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS ports (
id uuid NOT NULL,
port_code varchar(4) NOT NULL,
port_type varchar(1) NOT NULL,
port_name varchar(100) NOT NULL,
created_at timestamp NOT NULL DEFAULT NOW(),
updated_at timestamp NOT NULL DEFAULT NOW(),
CONSTRAINT port_pkey PRIMARY KEY(id),
CONSTRAINT unique_port_code UNIQUE (port_code),
CONSTRAINT chk_port_type CHECK (port_type IN ('A', 'S', 'B'))
);
COMMENT ON TABLE ports IS 'Stores ports identification data';
COMMENT ON COLUMN ports.port_code IS 'The 4 digit port code';
COMMENT ON COLUMN ports.port_type IS 'The 1 char port type A, S, or B';
COMMENT ON COLUMN ports.port_name IS 'The name of the port';
ALTER TABLE mto_service_items ADD COLUMN IF NOT EXISTS poe_location_id uuid;
ALTER TABLE mto_service_items ADD CONSTRAINT fk_poe_location_id FOREIGN KEY (poe_location_id) REFERENCES ports (id);
ALTER TABLE mto_service_items ADD COLUMN IF NOT EXISTS pod_location_id uuid;
ALTER TABLE mto_service_items ADD CONSTRAINT fk_pod_location_id FOREIGN KEY (pod_location_id) REFERENCES ports (id);
COMMENT ON COLUMN mto_service_items.poe_location_id IS 'Stores the POE location id for port of embarkation';
COMMENT ON COLUMN mto_service_items.pod_location_id IS 'Stores the POD location id for port of debarkation';
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
CREATE TABLE IF NOT EXISTS port_location (
id uuid NOT NULL,
port_id uuid NOT NULL
CONSTRAINT fk_port_id_port REFERENCES ports (id),
cities_id uuid NOT NULL
CONSTRAINT fk_cities_id_re_cities REFERENCES re_cities (id),
us_post_region_cities_id uuid NOT NULL
CONSTRAINT fk_us_post_region_cities_id_us_post_region_cities REFERENCES us_post_region_cities (id),
country_id uuid NOT NULL
CONSTRAINT fk_country_id_re_countries REFERENCES re_countries (id),
is_active bool DEFAULT TRUE,
created_at timestamp NOT NULL DEFAULT NOW(),
updated_at timestamp NOT NULL DEFAULT NOW(),
CONSTRAINT port_location_pkey PRIMARY KEY(id)
);

COMMENT ON TABLE port_location IS 'Stores the port location information';
COMMENT ON COLUMN port_location.port_id IS 'The ID for the port code references port';
COMMENT ON COLUMN port_location.cities_id IS 'The ID of the city';
COMMENT ON COLUMN port_location.us_post_region_cities_id IS 'The ID of the us postal regional city';
COMMENT ON COLUMN port_location.country_id IS 'The ID for the country';
COMMENT ON COLUMN port_location.is_active IS 'Bool for the active flag';
469 changes: 469 additions & 0 deletions migrations/app/schema/20241023184437_insert_ports.up.sql

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions pkg/models/mto_service_items.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ type MTOServiceItem struct {
PricingEstimate *unit.Cents `db:"pricing_estimate"`
StandaloneCrate *bool `db:"standalone_crate"`
LockedPriceCents *unit.Cents `db:"locked_price_cents"`
POELocation *Port `belongs_to:"port" fk_id:"poe_location_id"`
POELocationID *uuid.UUID `db:"poe_location_id"`
PODLocation *Port `belongs_to:"port" fk_id:"pod_location_id"`
PODLocationID *uuid.UUID `db:"pod_location_id"`
}

// MTOServiceItemSingle is an object representing a single column in the service items table
Expand Down Expand Up @@ -99,6 +103,8 @@ type MTOServiceItemSingle struct {
CustomerExpenseReason *string `db:"customer_expense_reason"`
SITDeliveryMiles *unit.Miles `db:"sit_delivery_miles"`
PricingEstimate *unit.Cents `db:"pricing_estimate"`
POELocationID *uuid.UUID `db:"poe_location_id"`
PODLocationID *uuid.UUID `db:"pod_location_id"`
}

// TableName overrides the table name used by Pop.
Expand Down
4 changes: 4 additions & 0 deletions pkg/models/mto_service_items_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@ func (suite *ModelSuite) TestMTOServiceItemValidation() {
moveTaskOrderID := uuid.Must(uuid.NewV4())
mtoShipmentID := uuid.Must(uuid.NewV4())
reServiceID := uuid.Must(uuid.NewV4())
poeLocationID := uuid.Must(uuid.NewV4())
podLocationID := uuid.Must(uuid.NewV4())

validMTOServiceItem := models.MTOServiceItem{
MoveTaskOrderID: moveTaskOrderID,
MTOShipmentID: &mtoShipmentID,
ReServiceID: reServiceID,
Status: models.MTOServiceItemStatusSubmitted,
POELocationID: &poeLocationID,
PODLocationID: &podLocationID,
}
expErrors := map[string][]string{}
suite.verifyValidationErrors(&validMTOServiceItem, expErrors)
Expand Down
52 changes: 52 additions & 0 deletions pkg/models/port.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package models

import (
"time"

"github.com/gobuffalo/pop/v6"
"github.com/gobuffalo/validate/v3"
"github.com/gobuffalo/validate/v3/validators"
"github.com/gofrs/uuid"
)

// PortType represents the type of port
type PortType string

// String is a string PortType
func (p PortType) String() string {
return string(p)
}

const (
PortTypeA PortType = "A"
PortTypeS PortType = "S"
PortTypeB PortType = "B"
)

var validPortType = []string{
string(PortTypeA),
string(PortTypeS),
string(PortTypeB),
}

type Port struct {
ID uuid.UUID `json:"id" db:"id" rw:"r"`
PortCode string `json:"port_code" db:"port_code" rw:"r"`
PortType PortType `json:"port_type" db:"port_type" rw:"r"`
PortName string `json:"port_name" db:"port_name" rw:"r"`
CreatedAt time.Time `json:"created_at" db:"created_at" rw:"r"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at" rw:"r"`
}

func (p Port) TableName() string {
return "ports"
}

// Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method.
func (p *Port) Validate(_ *pop.Connection) (*validate.Errors, error) {
return validate.Validate(
&validators.StringIsPresent{Field: p.PortCode, Name: "PortCode"},
&validators.StringInclusion{Field: p.PortType.String(), Name: "PortType", List: validPortType},
&validators.StringIsPresent{Field: p.PortName, Name: "PortName"},
), nil
}
35 changes: 35 additions & 0 deletions pkg/models/port_location.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package models

import (
"time"

"github.com/gobuffalo/pop/v6"
"github.com/gobuffalo/validate/v3"
"github.com/gobuffalo/validate/v3/validators"
"github.com/gofrs/uuid"
)

type PortLocation struct {
ID uuid.UUID `json:"id" db:"id"`
PortId uuid.UUID `json:"port_id" db:"port_id"`
CitiesId uuid.UUID `json:"cities_id" db:"cities_id"`
UsPostRegionCitiesId uuid.UUID `json:"us_post_region_cities_id" db:"us_post_region_cities_id"`
CountryId uuid.UUID `json:"country_id" db:"country_id"`
IsActive *bool `json:"is_active" db:"is_active"`
CreatedAt time.Time `json:"created_at" db:"created_at"`
UpdatedAt time.Time `json:"updated_at" db:"updated_at"`
}

func (l PortLocation) TableName() string {
return "port_locations"
}

// Validate gets run every time you call a "pop.Validate*" (pop.ValidateAndSave, pop.ValidateAndCreate, pop.ValidateAndUpdate) method.
func (p *PortLocation) Validate(_ *pop.Connection) (*validate.Errors, error) {
return validate.Validate(
&validators.UUIDIsPresent{Field: p.PortId, Name: "PortID"},
&validators.UUIDIsPresent{Field: p.CitiesId, Name: "CitiesID"},
&validators.UUIDIsPresent{Field: p.UsPostRegionCitiesId, Name: "UsPostRegionCitiesID"},
&validators.UUIDIsPresent{Field: p.CountryId, Name: "CountryID"},
), nil
}
42 changes: 42 additions & 0 deletions pkg/models/port_location_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package models_test

import (
"time"

"github.com/gofrs/uuid"

"github.com/transcom/mymove/pkg/models"
)

func (suite *ModelSuite) TestPortLocationValidation() {
suite.Run("test valid PortLocation", func() {
validPortLocation := models.PortLocation{
ID: uuid.Must(uuid.NewV4()),
PortId: uuid.Must(uuid.NewV4()),
CitiesId: uuid.Must(uuid.NewV4()),
UsPostRegionCitiesId: uuid.Must(uuid.NewV4()),
CountryId: uuid.Must(uuid.NewV4()),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
expErrors := map[string][]string{}
suite.verifyValidationErrors(&validPortLocation, expErrors)
})

suite.Run("test missing required fields", func() {
invalidPortLocation := models.PortLocation{
ID: uuid.Must(uuid.NewV4()),
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}

expErrors := map[string][]string{
"port_id": {"PortID can not be blank."},
"cities_id": {"CitiesID can not be blank."},
"us_post_region_cities_id": {"UsPostRegionCitiesID can not be blank."},
"country_id": {"CountryID can not be blank."},
}

suite.verifyValidationErrors(&invalidPortLocation, expErrors)
})
}
55 changes: 55 additions & 0 deletions pkg/models/port_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package models_test

import (
"time"

"github.com/gofrs/uuid"

"github.com/transcom/mymove/pkg/models"
)

func (suite *ModelSuite) TestPortValidation() {
suite.Run("test valid Port", func() {
validPort := models.Port{
ID: uuid.Must(uuid.NewV4()),
PortCode: "1234",
PortType: models.PortTypeA,
PortName: "Valid port name",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
expErrors := map[string][]string{}
suite.verifyValidationErrors(&validPort, expErrors)
})

suite.Run("test missing required fields", func() {
invalidPort := models.Port{
ID: uuid.Must(uuid.NewV4()),
PortType: models.PortTypeA,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}

expErrors := map[string][]string{
"port_code": {"PortCode can not be blank."},
"port_name": {"PortName can not be blank."},
}

suite.verifyValidationErrors(&invalidPort, expErrors)
})

suite.Run("test invalid port type", func() {
invalidPort := models.Port{
ID: uuid.Must(uuid.NewV4()),
PortType: "I", //'I' for invalid
PortCode: "1234",
PortName: "Invalid port type",
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
expErrors := map[string][]string{
"port_type": {"PortType is not in the list [A, S, B]."},
}
suite.verifyValidationErrors(&invalidPort, expErrors)
})
}
2 changes: 1 addition & 1 deletion scripts/db-truncate
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ DO \$\$ DECLARE
r RECORD;
BEGIN
FOR r IN (SELECT tablename FROM pg_tables WHERE schemaname = current_schema()
AND tablename NOT IN ('us_post_region_cities', 're_countries', 're_states', 're_cities', 're_us_post_regions', 're_oconus_rate_areas', 're_rate_areas', 're_intl_transit_times')) LOOP
AND tablename NOT IN ('us_post_region_cities', 're_countries', 're_states', 're_cities', 're_us_post_regions', 're_oconus_rate_areas', 're_rate_areas', 're_intl_transit_times', 'ports')) LOOP
EXECUTE 'TRUNCATE TABLE ' || quote_ident(r.tablename) || ' CASCADE';
END LOOP;
END \$\$;
Expand Down