From c97e069c36fc8d4af437e383bf63317ef82e2197 Mon Sep 17 00:00:00 2001 From: Michael Inthavongsay Date: Thu, 8 Aug 2024 18:27:59 +0000 Subject: [PATCH 01/12] customer: date validation on date selection --- pkg/gen/internalapi/configure_mymove.go | 5 + pkg/gen/internalapi/embedded_spec.go | 182 +++++++++ .../calendar/is_date_weekend_holiday.go | 58 +++ .../is_date_weekend_holiday_parameters.go | 129 +++++++ .../is_date_weekend_holiday_responses.go | 239 ++++++++++++ .../is_date_weekend_holiday_urlbuilder.go | 109 ++++++ .../internaloperations/mymove_api.go | 12 + .../is_date_weekend_holiday_info.go | 148 ++++++++ pkg/handlers/internalapi/api.go | 4 + pkg/handlers/internalapi/calendar.go | 24 ++ pkg/handlers/internalapi/calendar_test.go | 42 +++ pkg/services/calendar.go | 22 ++ .../calendar/date_selection_checker.go | 30 ++ .../calendar/date_selection_checker_test.go | 50 +++ .../progear_weight_ticket_service_test.go | 23 ++ pkg/services/mocks/DateSelectionChecker.go | 57 +++ .../MtoShipmentForm/MtoShipmentForm.jsx | 72 +++- .../MtoShipmentForm/MtoShipmentForm.test.jsx | 357 +++++++++++++++++- src/services/internalApi.js | 11 + src/shared/calendar.js | 55 +++ swagger-def/internal.yaml | 60 +++ swagger/internal.yaml | 62 +++ 22 files changed, 1742 insertions(+), 9 deletions(-) create mode 100644 pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday.go create mode 100644 pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_parameters.go create mode 100644 pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_responses.go create mode 100644 pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_urlbuilder.go create mode 100644 pkg/gen/internalmessages/is_date_weekend_holiday_info.go create mode 100644 pkg/services/calendar.go create mode 100644 pkg/services/calendar/date_selection_checker.go create mode 100644 pkg/services/calendar/date_selection_checker_test.go create mode 100644 pkg/services/calendar/progear_weight_ticket_service_test.go create mode 100644 pkg/services/mocks/DateSelectionChecker.go create mode 100644 src/shared/calendar.js diff --git a/pkg/gen/internalapi/configure_mymove.go b/pkg/gen/internalapi/configure_mymove.go index 1295e8d8ac8..f9737847f6a 100644 --- a/pkg/gen/internalapi/configure_mymove.go +++ b/pkg/gen/internalapi/configure_mymove.go @@ -220,6 +220,11 @@ func configureAPI(api *internaloperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation certification.IndexSignedCertification has not yet been implemented") }) } + if api.CalendarIsDateWeekendHolidayHandler == nil { + api.CalendarIsDateWeekendHolidayHandler = calendar.IsDateWeekendHolidayHandlerFunc(func(params calendar.IsDateWeekendHolidayParams) middleware.Responder { + return middleware.NotImplemented("operation calendar.IsDateWeekendHoliday has not yet been implemented") + }) + } if api.UsersIsLoggedInUserHandler == nil { api.UsersIsLoggedInUserHandler = users.IsLoggedInUserHandlerFunc(func(params users.IsLoggedInUserParams) middleware.Responder { return middleware.NotImplemented("operation users.IsLoggedInUser has not yet been implemented") diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index 24f5a4fdd47..a0b59d0f895 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -288,6 +288,59 @@ func init() { } } }, + "/calendar/{countryCode}/is-weekend-holiday/{date}": { + "get": { + "description": "Utility API to determine if input date falls on weekend and/or holiday.\n", + "produces": [ + "application/json" + ], + "tags": [ + "calendar" + ], + "summary": "Validate move date selection", + "operationId": "isDateWeekendHoliday", + "parameters": [ + { + "enum": [ + "US" + ], + "type": "string", + "description": "country code for context of date", + "name": "countryCode", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "date", + "description": "input date to determine if weekend/holiday for given country.", + "name": "date", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully determine if given date is weekend and/or holiday for given country.", + "schema": { + "$ref": "#/definitions/IsDateWeekendHolidayInfo" + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "401": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + } + }, "/documents": { "post": { "description": "Documents represent a physical artifact such as a scanned document or a PDF file", @@ -4244,6 +4297,38 @@ func init() { } } }, + "IsDateWeekendHolidayInfo": { + "type": "object", + "required": [ + "country_code", + "country_name", + "date", + "is_weekend", + "is_holiday" + ], + "properties": { + "country_code": { + "type": "string" + }, + "country_name": { + "type": "string" + }, + "date": { + "type": "string", + "format": "date", + "example": "2018-09-25" + }, + "details": { + "type": "string" + }, + "is_holiday": { + "type": "boolean" + }, + "is_weekend": { + "type": "boolean" + } + } + }, "LoggedInUserPayload": { "type": "object", "required": [ @@ -7947,6 +8032,71 @@ func init() { } } }, + "/calendar/{countryCode}/is-weekend-holiday/{date}": { + "get": { + "description": "Utility API to determine if input date falls on weekend and/or holiday.\n", + "produces": [ + "application/json" + ], + "tags": [ + "calendar" + ], + "summary": "Validate move date selection", + "operationId": "isDateWeekendHoliday", + "parameters": [ + { + "enum": [ + "US" + ], + "type": "string", + "description": "country code for context of date", + "name": "countryCode", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "date", + "description": "input date to determine if weekend/holiday for given country.", + "name": "date", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully determine if given date is weekend and/or holiday for given country.", + "schema": { + "$ref": "#/definitions/IsDateWeekendHolidayInfo" + } + }, + "400": { + "description": "The request payload is invalid.", + "schema": { + "$ref": "#/definitions/ClientError" + } + }, + "401": { + "description": "The request was denied.", + "schema": { + "$ref": "#/definitions/ClientError" + } + }, + "404": { + "description": "The requested resource wasn't found.", + "schema": { + "$ref": "#/definitions/ClientError" + } + }, + "500": { + "description": "A server error occurred.", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/documents": { "post": { "description": "Documents represent a physical artifact such as a scanned document or a PDF file", @@ -12337,6 +12487,38 @@ func init() { } } }, + "IsDateWeekendHolidayInfo": { + "type": "object", + "required": [ + "country_code", + "country_name", + "date", + "is_weekend", + "is_holiday" + ], + "properties": { + "country_code": { + "type": "string" + }, + "country_name": { + "type": "string" + }, + "date": { + "type": "string", + "format": "date", + "example": "2018-09-25" + }, + "details": { + "type": "string" + }, + "is_holiday": { + "type": "boolean" + }, + "is_weekend": { + "type": "boolean" + } + } + }, "LoggedInUserPayload": { "type": "object", "required": [ diff --git a/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday.go b/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday.go new file mode 100644 index 00000000000..2391d9fec61 --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package calendar + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// IsDateWeekendHolidayHandlerFunc turns a function with the right signature into a is date weekend holiday handler +type IsDateWeekendHolidayHandlerFunc func(IsDateWeekendHolidayParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn IsDateWeekendHolidayHandlerFunc) Handle(params IsDateWeekendHolidayParams) middleware.Responder { + return fn(params) +} + +// IsDateWeekendHolidayHandler interface for that can handle valid is date weekend holiday params +type IsDateWeekendHolidayHandler interface { + Handle(IsDateWeekendHolidayParams) middleware.Responder +} + +// NewIsDateWeekendHoliday creates a new http.Handler for the is date weekend holiday operation +func NewIsDateWeekendHoliday(ctx *middleware.Context, handler IsDateWeekendHolidayHandler) *IsDateWeekendHoliday { + return &IsDateWeekendHoliday{Context: ctx, Handler: handler} +} + +/* + IsDateWeekendHoliday swagger:route GET /calendar/{countryCode}/is-weekend-holiday/{date} calendar isDateWeekendHoliday + +# Validate move date selection + +Utility API to determine if input date falls on weekend and/or holiday. +*/ +type IsDateWeekendHoliday struct { + Context *middleware.Context + Handler IsDateWeekendHolidayHandler +} + +func (o *IsDateWeekendHoliday) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewIsDateWeekendHolidayParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_parameters.go b/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_parameters.go new file mode 100644 index 00000000000..bb43341894d --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_parameters.go @@ -0,0 +1,129 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package calendar + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// NewIsDateWeekendHolidayParams creates a new IsDateWeekendHolidayParams object +// +// There are no default values defined in the spec. +func NewIsDateWeekendHolidayParams() IsDateWeekendHolidayParams { + + return IsDateWeekendHolidayParams{} +} + +// IsDateWeekendHolidayParams contains all the bound params for the is date weekend holiday operation +// typically these are obtained from a http.Request +// +// swagger:parameters isDateWeekendHoliday +type IsDateWeekendHolidayParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*country code for context of date + Required: true + In: path + */ + CountryCode string + /*input date to determine if weekend/holiday for given country. + Required: true + In: path + */ + Date strfmt.Date +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewIsDateWeekendHolidayParams() beforehand. +func (o *IsDateWeekendHolidayParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rCountryCode, rhkCountryCode, _ := route.Params.GetOK("countryCode") + if err := o.bindCountryCode(rCountryCode, rhkCountryCode, route.Formats); err != nil { + res = append(res, err) + } + + rDate, rhkDate, _ := route.Params.GetOK("date") + if err := o.bindDate(rDate, rhkDate, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindCountryCode binds and validates parameter CountryCode from path. +func (o *IsDateWeekendHolidayParams) bindCountryCode(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.CountryCode = raw + + if err := o.validateCountryCode(formats); err != nil { + return err + } + + return nil +} + +// validateCountryCode carries on validations for parameter CountryCode +func (o *IsDateWeekendHolidayParams) validateCountryCode(formats strfmt.Registry) error { + + if err := validate.EnumCase("countryCode", "path", o.CountryCode, []interface{}{"US"}, true); err != nil { + return err + } + + return nil +} + +// bindDate binds and validates parameter Date from path. +func (o *IsDateWeekendHolidayParams) bindDate(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + // Format: date + value, err := formats.Parse("date", raw) + if err != nil { + return errors.InvalidType("date", "path", "strfmt.Date", raw) + } + o.Date = *(value.(*strfmt.Date)) + + if err := o.validateDate(formats); err != nil { + return err + } + + return nil +} + +// validateDate carries on validations for parameter Date +func (o *IsDateWeekendHolidayParams) validateDate(formats strfmt.Registry) error { + + if err := validate.FormatOf("date", "path", "date", o.Date.String(), formats); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_responses.go b/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_responses.go new file mode 100644 index 00000000000..61070103010 --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_responses.go @@ -0,0 +1,239 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package calendar + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/internalmessages" +) + +// IsDateWeekendHolidayOKCode is the HTTP code returned for type IsDateWeekendHolidayOK +const IsDateWeekendHolidayOKCode int = 200 + +/* +IsDateWeekendHolidayOK Successfully determine if given date is weekend and/or holiday for given country. + +swagger:response isDateWeekendHolidayOK +*/ +type IsDateWeekendHolidayOK struct { + + /* + In: Body + */ + Payload *internalmessages.IsDateWeekendHolidayInfo `json:"body,omitempty"` +} + +// NewIsDateWeekendHolidayOK creates IsDateWeekendHolidayOK with default headers values +func NewIsDateWeekendHolidayOK() *IsDateWeekendHolidayOK { + + return &IsDateWeekendHolidayOK{} +} + +// WithPayload adds the payload to the is date weekend holiday o k response +func (o *IsDateWeekendHolidayOK) WithPayload(payload *internalmessages.IsDateWeekendHolidayInfo) *IsDateWeekendHolidayOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the is date weekend holiday o k response +func (o *IsDateWeekendHolidayOK) SetPayload(payload *internalmessages.IsDateWeekendHolidayInfo) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *IsDateWeekendHolidayOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// IsDateWeekendHolidayBadRequestCode is the HTTP code returned for type IsDateWeekendHolidayBadRequest +const IsDateWeekendHolidayBadRequestCode int = 400 + +/* +IsDateWeekendHolidayBadRequest The request payload is invalid. + +swagger:response isDateWeekendHolidayBadRequest +*/ +type IsDateWeekendHolidayBadRequest struct { + + /* + In: Body + */ + Payload *internalmessages.ClientError `json:"body,omitempty"` +} + +// NewIsDateWeekendHolidayBadRequest creates IsDateWeekendHolidayBadRequest with default headers values +func NewIsDateWeekendHolidayBadRequest() *IsDateWeekendHolidayBadRequest { + + return &IsDateWeekendHolidayBadRequest{} +} + +// WithPayload adds the payload to the is date weekend holiday bad request response +func (o *IsDateWeekendHolidayBadRequest) WithPayload(payload *internalmessages.ClientError) *IsDateWeekendHolidayBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the is date weekend holiday bad request response +func (o *IsDateWeekendHolidayBadRequest) SetPayload(payload *internalmessages.ClientError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *IsDateWeekendHolidayBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// IsDateWeekendHolidayUnauthorizedCode is the HTTP code returned for type IsDateWeekendHolidayUnauthorized +const IsDateWeekendHolidayUnauthorizedCode int = 401 + +/* +IsDateWeekendHolidayUnauthorized The request was denied. + +swagger:response isDateWeekendHolidayUnauthorized +*/ +type IsDateWeekendHolidayUnauthorized struct { + + /* + In: Body + */ + Payload *internalmessages.ClientError `json:"body,omitempty"` +} + +// NewIsDateWeekendHolidayUnauthorized creates IsDateWeekendHolidayUnauthorized with default headers values +func NewIsDateWeekendHolidayUnauthorized() *IsDateWeekendHolidayUnauthorized { + + return &IsDateWeekendHolidayUnauthorized{} +} + +// WithPayload adds the payload to the is date weekend holiday unauthorized response +func (o *IsDateWeekendHolidayUnauthorized) WithPayload(payload *internalmessages.ClientError) *IsDateWeekendHolidayUnauthorized { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the is date weekend holiday unauthorized response +func (o *IsDateWeekendHolidayUnauthorized) SetPayload(payload *internalmessages.ClientError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *IsDateWeekendHolidayUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(401) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// IsDateWeekendHolidayNotFoundCode is the HTTP code returned for type IsDateWeekendHolidayNotFound +const IsDateWeekendHolidayNotFoundCode int = 404 + +/* +IsDateWeekendHolidayNotFound The requested resource wasn't found. + +swagger:response isDateWeekendHolidayNotFound +*/ +type IsDateWeekendHolidayNotFound struct { + + /* + In: Body + */ + Payload *internalmessages.ClientError `json:"body,omitempty"` +} + +// NewIsDateWeekendHolidayNotFound creates IsDateWeekendHolidayNotFound with default headers values +func NewIsDateWeekendHolidayNotFound() *IsDateWeekendHolidayNotFound { + + return &IsDateWeekendHolidayNotFound{} +} + +// WithPayload adds the payload to the is date weekend holiday not found response +func (o *IsDateWeekendHolidayNotFound) WithPayload(payload *internalmessages.ClientError) *IsDateWeekendHolidayNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the is date weekend holiday not found response +func (o *IsDateWeekendHolidayNotFound) SetPayload(payload *internalmessages.ClientError) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *IsDateWeekendHolidayNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// IsDateWeekendHolidayInternalServerErrorCode is the HTTP code returned for type IsDateWeekendHolidayInternalServerError +const IsDateWeekendHolidayInternalServerErrorCode int = 500 + +/* +IsDateWeekendHolidayInternalServerError A server error occurred. + +swagger:response isDateWeekendHolidayInternalServerError +*/ +type IsDateWeekendHolidayInternalServerError struct { + + /* + In: Body + */ + Payload *internalmessages.Error `json:"body,omitempty"` +} + +// NewIsDateWeekendHolidayInternalServerError creates IsDateWeekendHolidayInternalServerError with default headers values +func NewIsDateWeekendHolidayInternalServerError() *IsDateWeekendHolidayInternalServerError { + + return &IsDateWeekendHolidayInternalServerError{} +} + +// WithPayload adds the payload to the is date weekend holiday internal server error response +func (o *IsDateWeekendHolidayInternalServerError) WithPayload(payload *internalmessages.Error) *IsDateWeekendHolidayInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the is date weekend holiday internal server error response +func (o *IsDateWeekendHolidayInternalServerError) SetPayload(payload *internalmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *IsDateWeekendHolidayInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_urlbuilder.go b/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_urlbuilder.go new file mode 100644 index 00000000000..6f9666b8702 --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/calendar/is_date_weekend_holiday_urlbuilder.go @@ -0,0 +1,109 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package calendar + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/strfmt" +) + +// IsDateWeekendHolidayURL generates an URL for the is date weekend holiday operation +type IsDateWeekendHolidayURL struct { + CountryCode string + Date strfmt.Date + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *IsDateWeekendHolidayURL) WithBasePath(bp string) *IsDateWeekendHolidayURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *IsDateWeekendHolidayURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *IsDateWeekendHolidayURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/calendar/{countryCode}/is-weekend-holiday/{date}" + + countryCode := o.CountryCode + if countryCode != "" { + _path = strings.Replace(_path, "{countryCode}", countryCode, -1) + } else { + return nil, errors.New("countryCode is required on IsDateWeekendHolidayURL") + } + + date := o.Date.String() + if date != "" { + _path = strings.Replace(_path, "{date}", date, -1) + } else { + return nil, errors.New("date is required on IsDateWeekendHolidayURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/internal" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *IsDateWeekendHolidayURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *IsDateWeekendHolidayURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *IsDateWeekendHolidayURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on IsDateWeekendHolidayURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on IsDateWeekendHolidayURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *IsDateWeekendHolidayURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/internalapi/internaloperations/mymove_api.go b/pkg/gen/internalapi/internaloperations/mymove_api.go index dcee8b1e2ca..b8eb64077b7 100644 --- a/pkg/gen/internalapi/internaloperations/mymove_api.go +++ b/pkg/gen/internalapi/internaloperations/mymove_api.go @@ -157,6 +157,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { CertificationIndexSignedCertificationHandler: certification.IndexSignedCertificationHandlerFunc(func(params certification.IndexSignedCertificationParams) middleware.Responder { return middleware.NotImplemented("operation certification.IndexSignedCertification has not yet been implemented") }), + CalendarIsDateWeekendHolidayHandler: calendar.IsDateWeekendHolidayHandlerFunc(func(params calendar.IsDateWeekendHolidayParams) middleware.Responder { + return middleware.NotImplemented("operation calendar.IsDateWeekendHoliday has not yet been implemented") + }), UsersIsLoggedInUserHandler: users.IsLoggedInUserHandlerFunc(func(params users.IsLoggedInUserParams) middleware.Responder { return middleware.NotImplemented("operation users.IsLoggedInUser has not yet been implemented") }), @@ -375,6 +378,8 @@ type MymoveAPI struct { BackupContactsIndexServiceMemberBackupContactsHandler backup_contacts.IndexServiceMemberBackupContactsHandler // CertificationIndexSignedCertificationHandler sets the operation handler for the index signed certification operation CertificationIndexSignedCertificationHandler certification.IndexSignedCertificationHandler + // CalendarIsDateWeekendHolidayHandler sets the operation handler for the is date weekend holiday operation + CalendarIsDateWeekendHolidayHandler calendar.IsDateWeekendHolidayHandler // UsersIsLoggedInUserHandler sets the operation handler for the is logged in user operation UsersIsLoggedInUserHandler users.IsLoggedInUserHandler // MtoShipmentListMTOShipmentsHandler sets the operation handler for the list m t o shipments operation @@ -622,6 +627,9 @@ func (o *MymoveAPI) Validate() error { if o.CertificationIndexSignedCertificationHandler == nil { unregistered = append(unregistered, "certification.IndexSignedCertificationHandler") } + if o.CalendarIsDateWeekendHolidayHandler == nil { + unregistered = append(unregistered, "calendar.IsDateWeekendHolidayHandler") + } if o.UsersIsLoggedInUserHandler == nil { unregistered = append(unregistered, "users.IsLoggedInUserHandler") } @@ -948,6 +956,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/calendar/{countryCode}/is-weekend-holiday/{date}"] = calendar.NewIsDateWeekendHoliday(o.context, o.CalendarIsDateWeekendHolidayHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/users/is_logged_in"] = users.NewIsLoggedInUser(o.context, o.UsersIsLoggedInUserHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/pkg/gen/internalmessages/is_date_weekend_holiday_info.go b/pkg/gen/internalmessages/is_date_weekend_holiday_info.go new file mode 100644 index 00000000000..fac465ba617 --- /dev/null +++ b/pkg/gen/internalmessages/is_date_weekend_holiday_info.go @@ -0,0 +1,148 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package internalmessages + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "context" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// IsDateWeekendHolidayInfo is date weekend holiday info +// +// swagger:model IsDateWeekendHolidayInfo +type IsDateWeekendHolidayInfo struct { + + // country code + // Required: true + CountryCode *string `json:"country_code"` + + // country name + // Required: true + CountryName *string `json:"country_name"` + + // date + // Example: 2018-09-25 + // Required: true + // Format: date + Date *strfmt.Date `json:"date"` + + // details + Details string `json:"details,omitempty"` + + // is holiday + // Required: true + IsHoliday *bool `json:"is_holiday"` + + // is weekend + // Required: true + IsWeekend *bool `json:"is_weekend"` +} + +// Validate validates this is date weekend holiday info +func (m *IsDateWeekendHolidayInfo) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCountryCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateCountryName(formats); err != nil { + res = append(res, err) + } + + if err := m.validateDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateIsHoliday(formats); err != nil { + res = append(res, err) + } + + if err := m.validateIsWeekend(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *IsDateWeekendHolidayInfo) validateCountryCode(formats strfmt.Registry) error { + + if err := validate.Required("country_code", "body", m.CountryCode); err != nil { + return err + } + + return nil +} + +func (m *IsDateWeekendHolidayInfo) validateCountryName(formats strfmt.Registry) error { + + if err := validate.Required("country_name", "body", m.CountryName); err != nil { + return err + } + + return nil +} + +func (m *IsDateWeekendHolidayInfo) validateDate(formats strfmt.Registry) error { + + if err := validate.Required("date", "body", m.Date); err != nil { + return err + } + + if err := validate.FormatOf("date", "body", "date", m.Date.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *IsDateWeekendHolidayInfo) validateIsHoliday(formats strfmt.Registry) error { + + if err := validate.Required("is_holiday", "body", m.IsHoliday); err != nil { + return err + } + + return nil +} + +func (m *IsDateWeekendHolidayInfo) validateIsWeekend(formats strfmt.Registry) error { + + if err := validate.Required("is_weekend", "body", m.IsWeekend); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this is date weekend holiday info based on context it is used +func (m *IsDateWeekendHolidayInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *IsDateWeekendHolidayInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *IsDateWeekendHolidayInfo) UnmarshalBinary(b []byte) error { + var res IsDateWeekendHolidayInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/handlers/internalapi/api.go b/pkg/handlers/internalapi/api.go index 058cfc049d2..5cee0bec961 100644 --- a/pkg/handlers/internalapi/api.go +++ b/pkg/handlers/internalapi/api.go @@ -15,6 +15,7 @@ import ( paperworkgenerator "github.com/transcom/mymove/pkg/paperwork" paymentrequesthelper "github.com/transcom/mymove/pkg/payment_request" "github.com/transcom/mymove/pkg/services/address" + dateservice "github.com/transcom/mymove/pkg/services/calendar" "github.com/transcom/mymove/pkg/services/fetch" "github.com/transcom/mymove/pkg/services/ghcrateengine" move "github.com/transcom/mymove/pkg/services/move" @@ -230,6 +231,9 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI mtoshipment.NewShipmentDeleter(moveTaskOrderUpdater, moveRouter), } + dateSelectionChecker := dateservice.NewDateSelectionChecker() + internalAPI.CalendarIsDateWeekendHolidayHandler = IsDateWeekendHolidayHandler{handlerConfig, dateSelectionChecker} + internalAPI.PpmCreateMovingExpenseHandler = CreateMovingExpenseHandler{handlerConfig, movingexpense.NewMovingExpenseCreator()} internalAPI.PpmUpdateMovingExpenseHandler = UpdateMovingExpenseHandler{handlerConfig, movingexpense.NewCustomerMovingExpenseUpdater(ppmEstimator)} internalAPI.PpmDeleteMovingExpenseHandler = DeleteMovingExpenseHandler{handlerConfig, movingexpense.NewMovingExpenseDeleter()} diff --git a/pkg/handlers/internalapi/calendar.go b/pkg/handlers/internalapi/calendar.go index d4c777a84cf..27bd4c49a28 100644 --- a/pkg/handlers/internalapi/calendar.go +++ b/pkg/handlers/internalapi/calendar.go @@ -11,6 +11,7 @@ import ( calendarop "github.com/transcom/mymove/pkg/gen/internalapi/internaloperations/calendar" "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/services" ) // ShowAvailableMoveDatesHandler returns the available move dates starting at a given date. @@ -51,3 +52,26 @@ func (h ShowAvailableMoveDatesHandler) Handle(params calendarop.ShowAvailableMov return calendarop.NewShowAvailableMoveDatesOK().WithPayload(&availableMoveDatesPayload), nil }) } + +type IsDateWeekendHolidayHandler struct { + handlers.HandlerConfig + services.DateSelectionChecker +} + +func (h IsDateWeekendHolidayHandler) Handle(params calendarop.IsDateWeekendHolidayParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + date := time.Time(params.Date) + info, err := h.DateSelectionChecker.IsDateWeekendHoliday(appCtx, params.CountryCode, date) + if err != nil { + return calendarop.NewIsDateWeekendHolidayInternalServerError().WithPayload(nil), nil + } + var isDateWeekendHolidayInfo internalmessages.IsDateWeekendHolidayInfo + isDateWeekendHolidayInfo.CountryCode = &info.CountryCode + isDateWeekendHolidayInfo.CountryName = &info.CountryName + isDateWeekendHolidayInfo.Date = handlers.FmtDate(info.Date) + isDateWeekendHolidayInfo.IsWeekend = &info.IsWeekend + isDateWeekendHolidayInfo.IsHoliday = &info.IsHoliday + return calendarop.NewIsDateWeekendHolidayOK().WithPayload(&isDateWeekendHolidayInfo), nil + }) +} diff --git a/pkg/handlers/internalapi/calendar_test.go b/pkg/handlers/internalapi/calendar_test.go index be5af85c637..5db5e311e14 100644 --- a/pkg/handlers/internalapi/calendar_test.go +++ b/pkg/handlers/internalapi/calendar_test.go @@ -1,12 +1,16 @@ package internalapi import ( + "fmt" "net/http/httptest" "time" "github.com/go-openapi/strfmt" + "github.com/stretchr/testify/mock" calendarop "github.com/transcom/mymove/pkg/gen/internalapi/internaloperations/calendar" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/services/mocks" ) func (suite *HandlerSuite) TestShowAvailableMoveDatesHandler() { @@ -85,3 +89,41 @@ func (suite *HandlerSuite) TestShowAvailableMoveDatesHandler() { suite.Equal(startDate, *okResponse.Payload.StartDate) suite.Equal(availableDates, okResponse.Payload.Available) } + +func (suite *HandlerSuite) TestIsDateSelectionWeekendHolidayHandler() { + expectedUrl := fmt.Sprintf("/calendar/%s/is-weekend-holiday/%s", "US", "2023-01-01") + req := httptest.NewRequest("GET", expectedUrl, nil) + + params := calendarop.IsDateWeekendHolidayParams{ + HTTPRequest: req, + } + + expectedCountryCode := "US" + expectedCountryName := "United States" + expectedDate := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + mockDateSelectionChecker := mocks.DateSelectionChecker{} + info := services.IsDateWeekendHolidayInfo{} + info.CountryCode = expectedCountryCode + info.CountryName = expectedCountryName + info.Date = expectedDate + info.IsHoliday = true + info.IsWeekend = true + + mockDateSelectionChecker.On("IsDateWeekendHoliday", + mock.AnythingOfType("*appcontext.appContext"), + mock.AnythingOfType("string"), + mock.AnythingOfType("time.Time"), + ).Return(&info, nil) + + showHandler := IsDateWeekendHolidayHandler{suite.HandlerConfig(), &mockDateSelectionChecker} + response := showHandler.Handle(params) + + suite.IsType(&calendarop.IsDateWeekendHolidayOK{}, response) + okResponse := response.(*calendarop.IsDateWeekendHolidayOK) + + suite.Equal(expectedCountryCode, *okResponse.Payload.CountryCode) + suite.Equal(expectedCountryName, *okResponse.Payload.CountryName) + suite.Equal(strfmt.Date(expectedDate), *okResponse.Payload.Date) + suite.True(*okResponse.Payload.IsHoliday) + suite.True(*okResponse.Payload.IsWeekend) +} diff --git a/pkg/services/calendar.go b/pkg/services/calendar.go new file mode 100644 index 00000000000..eabc2ebc14c --- /dev/null +++ b/pkg/services/calendar.go @@ -0,0 +1,22 @@ +package services + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" +) + +// DateSelectionChecker is the service object interface to determine business rule for given date +// +//go:generate mockery --name DateSelectionChecker +type DateSelectionChecker interface { + IsDateWeekendHoliday(appCtx appcontext.AppContext, countryCode string, date time.Time) (*IsDateWeekendHolidayInfo, error) +} + +type IsDateWeekendHolidayInfo struct { + CountryCode string + CountryName string + Date time.Time + IsWeekend bool + IsHoliday bool +} diff --git a/pkg/services/calendar/date_selection_checker.go b/pkg/services/calendar/date_selection_checker.go new file mode 100644 index 00000000000..94479e43e88 --- /dev/null +++ b/pkg/services/calendar/date_selection_checker.go @@ -0,0 +1,30 @@ +package calendar + +import ( + "time" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/dates" + "github.com/transcom/mymove/pkg/services" +) + +type dateSelectionChecker struct{} + +func NewDateSelectionChecker() services.DateSelectionChecker { + return &dateSelectionChecker{} +} + +func (g *dateSelectionChecker) IsDateWeekendHoliday(appCtx appcontext.AppContext, countryCode string, date time.Time) (*services.IsDateWeekendHolidayInfo, error) { + // Assume for now invocation is US based only + // TODO: query TRDM data to determine if date is weekend/holiday for particular country for international moves + var calendar = dates.NewUSCalendar() + isHoliday, _, _ := calendar.IsHoliday(date) + var isDateWeekendHolidayInfo = services.IsDateWeekendHolidayInfo{} + isDateWeekendHolidayInfo.CountryCode = countryCode + // TODO - look up country name. For now return US. + isDateWeekendHolidayInfo.CountryName = "United States" + isDateWeekendHolidayInfo.Date = date + isDateWeekendHolidayInfo.IsWeekend = date.Weekday() == time.Saturday || date.Weekday() == time.Sunday + isDateWeekendHolidayInfo.IsHoliday = isHoliday + return &isDateWeekendHolidayInfo, nil +} diff --git a/pkg/services/calendar/date_selection_checker_test.go b/pkg/services/calendar/date_selection_checker_test.go new file mode 100644 index 00000000000..dce071a1edc --- /dev/null +++ b/pkg/services/calendar/date_selection_checker_test.go @@ -0,0 +1,50 @@ +package calendar + +import ( + "time" +) + +func (suite *CalendarSuite) TestDateSelectionChecker() { + expectedCountryCode := "US" + expectedCountryName := "United States" + service := NewDateSelectionChecker() + suite.Run("date is both holiday and weekend - US", func() { + expectedDate := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + info, _ := service.IsDateWeekendHoliday(suite.AppContextForTest(), expectedCountryCode, expectedDate) + suite.Equal(expectedCountryCode, info.CountryCode) + suite.Equal(expectedCountryName, info.CountryName) + suite.Equal(expectedDate, info.Date) + suite.True(info.IsHoliday) + suite.True(info.IsWeekend) + }) + + suite.Run("date is only weekend - US", func() { + expectedDate := time.Date(2024, 8, 10, 0, 0, 0, 0, time.UTC) + info, _ := service.IsDateWeekendHoliday(suite.AppContextForTest(), expectedCountryCode, expectedDate) + suite.Equal(expectedCountryCode, info.CountryCode) + suite.Equal(expectedCountryName, info.CountryName) + suite.Equal(expectedDate, info.Date) + suite.False(info.IsHoliday) + suite.True(info.IsWeekend) + }) + + suite.Run("date is only holiday - US", func() { + expectedDate := time.Date(2024, 7, 4, 0, 0, 0, 0, time.UTC) + info, _ := service.IsDateWeekendHoliday(suite.AppContextForTest(), expectedCountryCode, expectedDate) + suite.Equal(expectedCountryCode, info.CountryCode) + suite.Equal(expectedCountryName, info.CountryName) + suite.Equal(expectedDate, info.Date) + suite.True(info.IsHoliday) + suite.False(info.IsWeekend) + }) + + suite.Run("regular work day - US", func() { + expectedDate := time.Date(2024, 8, 6, 0, 0, 0, 0, time.UTC) + info, _ := service.IsDateWeekendHoliday(suite.AppContextForTest(), expectedCountryCode, expectedDate) + suite.Equal(expectedCountryCode, info.CountryCode) + suite.Equal(expectedCountryName, info.CountryName) + suite.Equal(expectedDate, info.Date) + suite.False(info.IsHoliday) + suite.False(info.IsWeekend) + }) +} diff --git a/pkg/services/calendar/progear_weight_ticket_service_test.go b/pkg/services/calendar/progear_weight_ticket_service_test.go new file mode 100644 index 00000000000..fe5f341e533 --- /dev/null +++ b/pkg/services/calendar/progear_weight_ticket_service_test.go @@ -0,0 +1,23 @@ +package calendar + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/transcom/mymove/pkg/testingsuite" +) + +type CalendarSuite struct { + *testingsuite.PopTestSuite +} + +func TestCalendarServiceSuite(t *testing.T) { + ts := &CalendarSuite{ + PopTestSuite: testingsuite.NewPopTestSuite(testingsuite.CurrentPackage(), testingsuite.WithPerTestTransaction()), + } + + suite.Run(t, ts) + + ts.PopTestSuite.TearDown() +} diff --git a/pkg/services/mocks/DateSelectionChecker.go b/pkg/services/mocks/DateSelectionChecker.go new file mode 100644 index 00000000000..d5f42ea3f12 --- /dev/null +++ b/pkg/services/mocks/DateSelectionChecker.go @@ -0,0 +1,57 @@ +// Code generated by mockery. DO NOT EDIT. + +package mocks + +import ( + mock "github.com/stretchr/testify/mock" + appcontext "github.com/transcom/mymove/pkg/appcontext" + + services "github.com/transcom/mymove/pkg/services" + + time "time" +) + +// DateSelectionChecker is an autogenerated mock type for the DateSelectionChecker type +type DateSelectionChecker struct { + mock.Mock +} + +// IsDateWeekendHoliday provides a mock function with given fields: appCtx, countryCode, date +func (_m *DateSelectionChecker) IsDateWeekendHoliday(appCtx appcontext.AppContext, countryCode string, date time.Time) (*services.IsDateWeekendHolidayInfo, error) { + ret := _m.Called(appCtx, countryCode, date) + + var r0 *services.IsDateWeekendHolidayInfo + var r1 error + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time) (*services.IsDateWeekendHolidayInfo, error)); ok { + return rf(appCtx, countryCode, date) + } + if rf, ok := ret.Get(0).(func(appcontext.AppContext, string, time.Time) *services.IsDateWeekendHolidayInfo); ok { + r0 = rf(appCtx, countryCode, date) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*services.IsDateWeekendHolidayInfo) + } + } + + if rf, ok := ret.Get(1).(func(appcontext.AppContext, string, time.Time) error); ok { + r1 = rf(appCtx, countryCode, date) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// NewDateSelectionChecker creates a new instance of DateSelectionChecker. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewDateSelectionChecker(t interface { + mock.TestingT + Cleanup(func()) +}) *DateSelectionChecker { + mock := &DateSelectionChecker{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx index ab68d8d1221..20b0ac4150e 100644 --- a/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx +++ b/src/components/Customer/MtoShipmentForm/MtoShipmentForm.jsx @@ -1,4 +1,4 @@ -import React, { Component } from 'react'; +import React, { Component, useState, useEffect } from 'react'; import { bool, func, string } from 'prop-types'; import { Field, Formik } from 'formik'; import { generatePath } from 'react-router-dom'; @@ -30,7 +30,12 @@ import ShipmentTag from 'components/ShipmentTag/ShipmentTag'; import { customerRoutes } from 'constants/routes'; import { roleTypes } from 'constants/userRoles'; import { shipmentForm } from 'content/shipments'; -import { createMTOShipment, getResponseError, patchMTOShipment } from 'services/internalApi'; +import { + createMTOShipment, + getResponseError, + patchMTOShipment, + dateSelectionIsWeekendHoliday, +} from 'services/internalApi'; import { SHIPMENT_OPTIONS } from 'shared/constants'; import formStyles from 'styles/form.module.scss'; import { AddressShape, SimpleAddressShape } from 'types/address'; @@ -42,6 +47,7 @@ import { validateDate } from 'utils/validation'; import withRouter from 'utils/routing'; import { ORDERS_TYPE } from 'constants/orders'; import { isBooleanFlagEnabled } from 'utils/featureFlags'; +import { dateSelectionWeekendHolidayCheck } from 'shared/calendar'; const blankAddress = { address: { @@ -92,6 +98,7 @@ class MtoShipmentForm extends Component { mtoShipment, updateMTOShipment, } = this.props; + const { moveId } = params; const isNTSR = shipmentType === SHIPMENT_OPTIONS.NTSR; @@ -170,6 +177,7 @@ class MtoShipmentForm extends Component { currentResidence, router: { params, navigate }, } = this.props; + const { moveId } = params; const { isTertiaryAddressEnabled } = this.state; const { errorMessage } = this.state; @@ -199,6 +207,8 @@ class MtoShipmentForm extends Component { hasSecondaryDelivery, hasTertiaryPickup, hasTertiaryDelivery, + pickup, + delivery, } = values; const handleUseCurrentResidenceChange = (e) => { @@ -234,6 +244,54 @@ class MtoShipmentForm extends Component { } }; + const [isPreferredPickupDateAlertVisible, setIsPreferredPickupDateAlertVisible] = useState(false); + const [isPreferredDeliveryDateAlertVisible, setIsPreferredDeliveryDateAlertVisible] = useState(false); + const [preferredPickupDateAlertMessage, setPreferredPickupDateAlertMessage] = useState(''); + const [preferredDeliveryDateAlertMessage, setPreferredDeliveryDateAlertMessage] = useState(''); + const DEFAULT_COUNTRY_CODE = 'US'; + + const onDateSelectionErrorHandler = (e) => { + const { response } = e; + const msg = getResponseError(response, 'failed to retrieve date selection weekend/holiday info'); + this.setState({ errorMessage: msg }); + }; + + useEffect(() => { + if (pickup?.requestedDate !== '') { + const preferredPickupDateSelectionHandler = (countryCode, date) => { + dateSelectionWeekendHolidayCheck( + dateSelectionIsWeekendHoliday, + countryCode, + date, + 'Preferred pickup date', + setPreferredPickupDateAlertMessage, + setIsPreferredPickupDateAlertVisible, + onDateSelectionErrorHandler, + ); + }; + const dateSelection = new Date(pickup.requestedDate); + preferredPickupDateSelectionHandler(DEFAULT_COUNTRY_CODE, dateSelection); + } + }, [pickup.requestedDate]); + + useEffect(() => { + if (delivery?.requestedDate !== '') { + const preferredDeliveryDateSelectionHandler = (countryCode, date) => { + dateSelectionWeekendHolidayCheck( + dateSelectionIsWeekendHoliday, + countryCode, + date, + 'Preferred delivery date', + setPreferredDeliveryDateAlertMessage, + setIsPreferredDeliveryDateAlertVisible, + onDateSelectionErrorHandler, + ); + }; + const dateSelection = new Date(delivery.requestedDate); + preferredDeliveryDateSelectionHandler(DEFAULT_COUNTRY_CODE, dateSelection); + } + }, [delivery.requestedDate]); + return ( @@ -265,6 +323,11 @@ class MtoShipmentForm extends Component { pickup/load date should be your latest preferred pickup/load date, or the date you need to be out of your origin residence. + {isPreferredPickupDateAlertVisible && ( + + {preferredPickupDateAlertMessage} + + )} + {isPreferredDeliveryDateAlertVisible && ( + + {preferredDeliveryDateAlertMessage} + + )} ({ createMTOShipment: jest.fn(), getResponseError: jest.fn(), patchMTOShipment: jest.fn(), + dateSelectionIsWeekendHoliday: jest.fn().mockImplementation(() => Promise.resolve()), })); const moveId = uuidv4(); @@ -35,6 +41,7 @@ const defaultProps = { showLoggedInUser: jest.fn(), createMTOShipment: jest.fn(), updateMTOShipment: jest.fn(), + dateSelectionIsWeekendHoliday: jest.fn().mockImplementation(() => Promise.resolve()), newDutyLocationAddress: { city: 'Fort Benning', state: 'GA', @@ -334,7 +341,15 @@ describe('MtoShipmentForm component', () => { }; createMTOShipment.mockImplementation(() => Promise.resolve(expectedCreateResponse)); - + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: false, + is_holiday: false, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); renderMtoShipmentForm(); const pickupDateInput = await screen.findByLabelText('Preferred pickup date'); @@ -388,7 +403,15 @@ describe('MtoShipmentForm component', () => { const errorResponse = { response: { errorMessage } }; createMTOShipment.mockImplementation(() => Promise.reject(errorResponse)); getResponseError.mockImplementation(() => errorMessage); - + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); renderMtoShipmentForm(); const pickupDateInput = await screen.findByLabelText('Preferred pickup date'); @@ -458,8 +481,16 @@ describe('MtoShipmentForm component', () => { }; it('renders the HHG shipment form with pre-filled values', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); - expect(await screen.findByLabelText('Preferred pickup date')).toHaveValue('01 Aug 2021'); expect(screen.getByLabelText('Use my current address')).not.toBeChecked(); expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue('812 S 129th St'); @@ -479,6 +510,17 @@ describe('MtoShipmentForm component', () => { 'Are there things about this shipment that your counselor or movers should discuss with you?', ), ).toHaveValue('mock remarks'); + + expect( + screen.getByText( + /Preferred pickup date 01 Aug 2021 is on a holiday and weekend in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + expect( + screen.getAllByText( + 'Preferred pickup date 01 Aug 2021 is on a holiday and weekend in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date.', + ), + ).toHaveLength(1); }); it('renders the HHG shipment form with pre-filled secondary addresses', async () => { @@ -499,7 +541,15 @@ describe('MtoShipmentForm component', () => { postalCode: mockMtoShipment.destinationAddress.postalCode, }, }; - + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); renderMtoShipmentForm({ isCreatePage: false, mtoShipment: shipment }); expect(await screen.findByTitle('Yes, I have a second pickup location')).toBeChecked(); @@ -543,6 +593,15 @@ describe('MtoShipmentForm component', () => { ])( 'does not allow the user to save the form if the %s field on a secondary addreess is the only one filled out', async (fieldName, text) => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: false, + is_holiday: false, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); // Verify that the form is good to submit by checking that the save button is not disabled. @@ -581,6 +640,15 @@ describe('MtoShipmentForm component', () => { // Similar test as above, but with the state input. // Extracted out since the state field is not a text input. it('does not allow the user to save the form if the state field on a secondary addreess is the only one filled out', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: false, + is_holiday: false, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); // Verify that the form is good to submit by checking that the save button is not disabled. @@ -616,6 +684,15 @@ describe('MtoShipmentForm component', () => { }); it('goes back when the cancel button is clicked', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); const cancelButton = await screen.findByRole('button', { name: 'Cancel' }); @@ -671,7 +748,15 @@ describe('MtoShipmentForm component', () => { }; patchMTOShipment.mockImplementation(() => Promise.resolve(expectedUpdateResponse)); - + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); const pickupAddress1Input = screen.getAllByLabelText('Address 1')[0]; @@ -721,7 +806,15 @@ describe('MtoShipmentForm component', () => { const errorResponse = { response: { errorMessage } }; patchMTOShipment.mockImplementation(() => Promise.reject(errorResponse)); getResponseError.mockImplementation(() => errorMessage); - + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); const pickupAddress1Input = screen.getAllByLabelText('Address 1')[0]; @@ -758,6 +851,152 @@ describe('MtoShipmentForm component', () => { expect(await screen.findByText(errorMessage)).toBeInTheDocument(); }); + + it('renders the HHG shipment form with pre-filled values', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText('Preferred pickup date')).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText('Use my current address')).not.toBeChecked(); + expect(screen.getAllByLabelText('Address 1')[0]).toHaveValue('812 S 129th St'); + expect(screen.getAllByLabelText(/Address 2/)[0]).toHaveValue(''); + expect(screen.getAllByLabelText('City')[0]).toHaveValue('San Antonio'); + expect(screen.getAllByLabelText('State')[0]).toHaveValue('TX'); + expect(screen.getAllByLabelText('ZIP')[0]).toHaveValue('78234'); + expect(screen.getByLabelText('Preferred delivery date')).toHaveValue('11 Aug 2021'); + expect(screen.getByTitle('Yes, I know my delivery address')).toBeChecked(); + expect(screen.getAllByLabelText('Address 1')[1]).toHaveValue('441 SW Rio de la Plata Drive'); + expect(screen.getAllByLabelText(/Address 2/)[1]).toHaveValue(''); + expect(screen.getAllByLabelText('City')[1]).toHaveValue('Tacoma'); + expect(screen.getAllByLabelText('State')[1]).toHaveValue('WA'); + expect(screen.getAllByLabelText('ZIP')[1]).toHaveValue('98421'); + expect( + screen.getByLabelText( + 'Are there things about this shipment that your counselor or movers should discuss with you?', + ), + ).toHaveValue('mock remarks'); + }); + + it('renders the HHG shipment with date validaton alerts for weekend and holiday', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United of States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText('Preferred pickup date')).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText('Preferred delivery date')).toHaveValue('11 Aug 2021'); + await waitFor(() => { + expect( + screen.getByText( + /Preferred pickup date 01 Aug 2021 is on a holiday and weekend in the United of States. This date, may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + expect( + screen.getByText( + /Preferred delivery date 11 Aug 2021 is on a holiday and weekend in the United of States. This date, may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + }); + }); + + it('renders the HHG shipment with date validaton alerts for weekend', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: false, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText('Preferred pickup date')).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText('Preferred delivery date')).toHaveValue('11 Aug 2021'); + await waitFor(() => { + expect( + screen.getByText( + /Preferred pickup date 01 Aug 2021 is on a weekend in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + expect( + screen.getByText( + /Preferred delivery date 11 Aug 2021 is on a weekend in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + }); + }); + + it('renders the HHG shipment with date validaton alerts for holiday', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: false, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText('Preferred pickup date')).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText('Preferred delivery date')).toHaveValue('11 Aug 2021'); + await waitFor(() => { + expect( + screen.getByText( + /Preferred pickup date 01 Aug 2021 is on a holiday in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + expect( + screen.getByText( + /Preferred delivery date 11 Aug 2021 is on a holiday in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + }); + }); + + it('renders the HHG shipment with no date validaton alerts for pickup/delivery', async () => { + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: false, + is_holiday: false, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderMtoShipmentForm({ isCreatePage: false, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText('Preferred pickup date')).toHaveValue('01 Aug 2021'); + expect(screen.getByLabelText('Preferred delivery date')).toHaveValue('11 Aug 2021'); + expect( + screen.getByLabelText( + 'Are there things about this shipment that your counselor or movers should discuss with you?', + ), + ).toHaveValue('mock remarks'); + + await waitFor(() => { + expect( + screen.queryAllByText( + 'Preferred pickup date 01 Aug 2021 is on a holiday in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date.', + ), + ).toHaveLength(0); + expect( + screen.queryAllByText( + 'Preferred delivery date 11 Aug 2021 is on a holiday in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date.', + ), + ).toHaveLength(0); + }); + }); }); describe('creating a new NTS shipment', () => { @@ -822,6 +1061,58 @@ describe('MtoShipmentForm component', () => { expect(queryByTestId('nts-what-to-expect')).toBeInTheDocument(); }); }); + + it('renders NTS with preferred pickup date alert for holiday and weekend', async () => { + const updatedAt = '2021-06-11T18:12:11.918Z'; + const mockMtoShipment = { + id: uuidv4(), + eTag: window.btoa(updatedAt), + createdAt: '2021-06-11T18:12:11.918Z', + updatedAt, + moveTaskOrderId: moveId, + customerRemarks: 'mock remarks', + requestedPickupDate: '2021-08-01', + requestedDeliveryDate: '2021-08-11', + pickupAddress: { + id: uuidv4(), + streetAddress1: '812 S 129th St', + city: 'San Antonio', + state: 'TX', + postalCode: '78234', + }, + destinationAddress: { + id: uuidv4(), + streetAddress1: '441 SW Rio de la Plata Drive', + city: 'Tacoma', + state: 'WA', + postalCode: '98421', + }, + }; + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderMtoShipmentForm({ isCreatePage: false, shipmentType: SHIPMENT_OPTIONS.NTS, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText('Preferred pickup date')).toHaveValue('01 Aug 2021'); + await waitFor(() => { + // only pickup date is available. delivery alert will never be present. + expect( + screen.getByText( + /Preferred pickup date 01 Aug 2021 is on a holiday and weekend in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + expect( + screen.queryAllByText( + 'Preferred delivery date 11 Aug 2021 is on a holiday in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date.', + ), + ).toHaveLength(0); + }); + }); }); describe('creating a new NTS-release shipment', () => { @@ -883,5 +1174,57 @@ describe('MtoShipmentForm component', () => { expect(queryByTestId('nts-what-to-expect')).not.toBeInTheDocument(); }); }); + + it('renders NTSR with preferred delivery date alert for holiday and weekend', async () => { + const updatedAt = '2021-06-11T18:12:11.918Z'; + const mockMtoShipment = { + id: uuidv4(), + eTag: window.btoa(updatedAt), + createdAt: '2021-06-11T18:12:11.918Z', + updatedAt, + moveTaskOrderId: moveId, + customerRemarks: 'mock remarks', + requestedPickupDate: '2021-08-01', + requestedDeliveryDate: '2021-08-11', + pickupAddress: { + id: uuidv4(), + streetAddress1: '812 S 129th St', + city: 'San Antonio', + state: 'TX', + postalCode: '78234', + }, + destinationAddress: { + id: uuidv4(), + streetAddress1: '441 SW Rio de la Plata Drive', + city: 'Tacoma', + state: 'WA', + postalCode: '98421', + }, + }; + const expectedDateSelectionIsWeekendHolidayResponse = { + country_code: 'US', + country_name: 'United States', + is_weekend: true, + is_holiday: true, + }; + dateSelectionIsWeekendHoliday.mockImplementation(() => + Promise.resolve({ data: JSON.stringify(expectedDateSelectionIsWeekendHolidayResponse) }), + ); + renderMtoShipmentForm({ isCreatePage: false, shipmentType: SHIPMENT_OPTIONS.NTSR, mtoShipment: mockMtoShipment }); + expect(await screen.findByLabelText('Preferred delivery date')).toHaveValue('11 Aug 2021'); + await waitFor(() => { + // only delivery date is available. pickup alert will never be present. + expect( + screen.queryAllByText( + 'Preferred pickup date 01 Aug 2021 is on a holiday and weekend in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date.', + ), + ).toHaveLength(0); + expect( + screen.getByText( + /Preferred delivery date 11 Aug 2021 is on a holiday and weekend in the United States. This date, may not be accepted. A government representative may not be available to provide assistance on this date./, + ), + ).toHaveClass('usa-alert__text'); + }); + }); }); }); diff --git a/src/services/internalApi.js b/src/services/internalApi.js index 501917b4d64..7997387fe87 100644 --- a/src/services/internalApi.js +++ b/src/services/internalApi.js @@ -537,3 +537,14 @@ export async function downloadPPMAOAPacket(ppmShipmentId) { export async function downloadPPMPaymentPacket(ppmShipmentId) { return makeInternalRequestRaw('ppm.showPaymentPacket', { ppmShipmentId }); } + +export async function dateSelectionIsWeekendHoliday(countryCode, date) { + return makeInternalRequestRaw( + 'calendar.isDateWeekendHoliday', + { + countryCode, + date, + }, + { normalize: false }, + ); +} diff --git a/src/shared/calendar.js b/src/shared/calendar.js new file mode 100644 index 00000000000..2c7563c5854 --- /dev/null +++ b/src/shared/calendar.js @@ -0,0 +1,55 @@ +import { formatDateForDatePicker } from 'shared/dates'; + +/** + * Function to handle date selection validation for input date for particular country. + * + * @param {function} dateSelectionIsWeekendHolidayAPI is webservice API + * @param {string} countryCode is country code for date + * @param {date} date is date to be verified + * @param {string} label is consumer label + * @param {function} setAlertMessageCallback is callback function to set alert validation message + * @param {function} setIsDateSelectionAlertVisibleCallback is callback function to set alert message visibility + * @param {function} onErrorCallback is callback function for on error handling + */ +export function dateSelectionWeekendHolidayCheck( + dateSelectionIsWeekendHolidayAPI, + countryCode, + date, + label, + setAlertMessageCallback, + setIsDateSelectionAlertVisibleCallback, + onErrorCallback, +) { + if (!isNaN(date.getTime())) { + // Format date to yyyy-mm-dd format + const dateSelection = date.toISOString().replace(/T.*/, ''); + dateSelectionIsWeekendHolidayAPI(countryCode, dateSelection) + .then((response) => { + const info = JSON.parse(response.data); + if (info.is_holiday || info.is_weekend) { + let type = ''; + if (info.is_holiday) { + type = 'holiday'; + } + if (info.is_weekend) { + type = 'weekend'; + } + if (info.is_holiday && info.is_weekend) { + type = 'holiday and weekend'; + } + + const countryLabel = info.country_code === 'US' ? `the ${info.country_name}` : info.country_name; + const message = `${label} ${formatDateForDatePicker( + dateSelection, + )} is on a ${type} in ${countryLabel}. This date, may not be accepted. A government representative may not be available to provide assistance on this date.`; + setAlertMessageCallback(message); + setIsDateSelectionAlertVisibleCallback(true); + } else if (!info.is_holiday && !info.is_weekend) { + setIsDateSelectionAlertVisibleCallback(false); + } + }) + .catch((error) => { + onErrorCallback(error); + }); + } +} diff --git a/swagger-def/internal.yaml b/swagger-def/internal.yaml index 8d0c9b4a472..4fac038c87f 100644 --- a/swagger-def/internal.yaml +++ b/swagger-def/internal.yaml @@ -2206,6 +2206,29 @@ definitions: format: date-time type: string readOnly: true + IsDateWeekendHolidayInfo: + type: object + properties: + country_code: + type: string + country_name: + type: string + date: + type: string + format: date + example: "2018-09-25" + is_weekend: + type: boolean + is_holiday: + type: boolean + details: + type: string + required: + - country_code + - country_name + - date + - is_weekend + - is_holiday paths: /feature-flags/user-boolean/{key}: post: @@ -4372,6 +4395,43 @@ paths: $ref: "#/responses/UnprocessableEntity" "500": description: internal server error + /calendar/{countryCode}/is-weekend-holiday/{date}: + get: + summary: Validate move date selection + description: | + Utility API to determine if input date falls on weekend and/or holiday. + produces: + - application/json + operationId: isDateWeekendHoliday + tags: + - calendar + parameters: + - description: country code for context of date + in: path + name: countryCode + required: true + type: string + enum: + - US + - description: input date to determine if weekend/holiday for given country. + in: path + name: date + required: true + type: string + format: date + responses: + "200": + description: Successfully determine if given date is weekend and/or holiday for given country. + schema: + $ref: "#/definitions/IsDateWeekendHolidayInfo" + "400": + $ref: "#/responses/InvalidRequest" + "401": + $ref: "#/responses/PermissionDenied" + "404": + $ref: "#/responses/NotFound" + "500": + $ref: "#/responses/ServerError" responses: InvalidRequest: description: The request payload is invalid. diff --git a/swagger/internal.yaml b/swagger/internal.yaml index a06ea2d0352..e50c01c5ccf 100644 --- a/swagger/internal.yaml +++ b/swagger/internal.yaml @@ -2239,6 +2239,29 @@ definitions: format: date-time type: string readOnly: true + IsDateWeekendHolidayInfo: + type: object + properties: + country_code: + type: string + country_name: + type: string + date: + type: string + format: date + example: '2018-09-25' + is_weekend: + type: boolean + is_holiday: + type: boolean + details: + type: string + required: + - country_code + - country_name + - date + - is_weekend + - is_holiday FeatureFlagBoolean: description: A feature flag type: object @@ -5821,6 +5844,45 @@ paths: $ref: '#/responses/UnprocessableEntity' '500': description: internal server error + /calendar/{countryCode}/is-weekend-holiday/{date}: + get: + summary: Validate move date selection + description: | + Utility API to determine if input date falls on weekend and/or holiday. + produces: + - application/json + operationId: isDateWeekendHoliday + tags: + - calendar + parameters: + - description: country code for context of date + in: path + name: countryCode + required: true + type: string + enum: + - US + - description: input date to determine if weekend/holiday for given country. + in: path + name: date + required: true + type: string + format: date + responses: + '200': + description: >- + Successfully determine if given date is weekend and/or holiday for + given country. + schema: + $ref: '#/definitions/IsDateWeekendHolidayInfo' + '400': + $ref: '#/responses/InvalidRequest' + '401': + $ref: '#/responses/PermissionDenied' + '404': + $ref: '#/responses/NotFound' + '500': + $ref: '#/responses/ServerError' responses: InvalidRequest: description: The request payload is invalid. From 6cda2b0aa8111747383218553fdca29a8af32029 Mon Sep 17 00:00:00 2001 From: Michael Inthavongsay Date: Thu, 8 Aug 2024 21:42:24 +0000 Subject: [PATCH 02/12] renamed test file --- ...ear_weight_ticket_service_test.go => calendar_service_test.go} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename pkg/services/calendar/{progear_weight_ticket_service_test.go => calendar_service_test.go} (100%) diff --git a/pkg/services/calendar/progear_weight_ticket_service_test.go b/pkg/services/calendar/calendar_service_test.go similarity index 100% rename from pkg/services/calendar/progear_weight_ticket_service_test.go rename to pkg/services/calendar/calendar_service_test.go From 37c25a66df09cacf14353a6e54d8480e15bde66f Mon Sep 17 00:00:00 2001 From: Michael Inthavongsay Date: Mon, 12 Aug 2024 12:29:59 +0000 Subject: [PATCH 03/12] office - date weekend/holiday validation --- pkg/gen/ghcapi/configure_mymove.go | 6 + pkg/gen/ghcapi/embedded_spec.go | 182 +++++++++++++ .../calendar/is_date_weekend_holiday.go | 58 +++++ .../is_date_weekend_holiday_parameters.go | 129 ++++++++++ .../is_date_weekend_holiday_responses.go | 239 ++++++++++++++++++ .../is_date_weekend_holiday_urlbuilder.go | 109 ++++++++ pkg/gen/ghcapi/ghcoperations/mymove_api.go | 13 + .../is_date_weekend_holiday_info.go | 148 +++++++++++ pkg/handlers/ghcapi/api.go | 4 + pkg/handlers/ghcapi/calendar.go | 36 +++ .../Office/ShipmentForm/ShipmentForm.jsx | 107 +++++++- src/services/ghcApi.js | 11 + swagger-def/ghc.yaml | 60 +++++ swagger/ghc.yaml | 62 +++++ 14 files changed, 1162 insertions(+), 2 deletions(-) create mode 100644 pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday.go create mode 100644 pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_parameters.go create mode 100644 pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_responses.go create mode 100644 pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_urlbuilder.go create mode 100644 pkg/gen/ghcmessages/is_date_weekend_holiday_info.go create mode 100644 pkg/handlers/ghcapi/calendar.go diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 780c3432a1d..84b141ca063 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -12,6 +12,7 @@ import ( "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/application_parameters" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/calendar" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/customer" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/customer_support_remarks" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/evaluation_reports" @@ -339,6 +340,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation transportation_office.GetTransportationOfficesOpen has not yet been implemented") }) } + if api.CalendarIsDateWeekendHolidayHandler == nil { + api.CalendarIsDateWeekendHolidayHandler = calendar.IsDateWeekendHolidayHandlerFunc(func(params calendar.IsDateWeekendHolidayParams) middleware.Responder { + return middleware.NotImplemented("operation calendar.IsDateWeekendHoliday has not yet been implemented") + }) + } if api.MtoServiceItemListMTOServiceItemsHandler == nil { api.MtoServiceItemListMTOServiceItemsHandler = mto_service_item.ListMTOServiceItemsHandlerFunc(func(params mto_service_item.ListMTOServiceItemsParams) middleware.Responder { return middleware.NotImplemented("operation mto_service_item.ListMTOServiceItems has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 341c5b71da9..7ed3b038617 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -73,6 +73,59 @@ func init() { } } }, + "/calendar/{countryCode}/is-weekend-holiday/{date}": { + "get": { + "description": "Utility API to determine if input date falls on weekend and/or holiday.\n", + "produces": [ + "application/json" + ], + "tags": [ + "calendar" + ], + "summary": "Validate move date selection", + "operationId": "isDateWeekendHoliday", + "parameters": [ + { + "enum": [ + "US" + ], + "type": "string", + "description": "country code for context of date", + "name": "countryCode", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "date", + "description": "input date to determine if weekend/holiday for given country.", + "name": "date", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully determine if given date is weekend and/or holiday for given country.", + "schema": { + "$ref": "#/definitions/IsDateWeekendHolidayInfo" + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "401": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + } + }, "/counseling/orders/{orderID}": { "patch": { "description": "All fields sent in this request will be set on the order referenced", @@ -7443,6 +7496,38 @@ func init() { } } }, + "IsDateWeekendHolidayInfo": { + "type": "object", + "required": [ + "country_code", + "country_name", + "date", + "is_weekend", + "is_holiday" + ], + "properties": { + "country_code": { + "type": "string" + }, + "country_name": { + "type": "string" + }, + "date": { + "type": "string", + "format": "date", + "example": "2018-09-25" + }, + "details": { + "type": "string" + }, + "is_holiday": { + "type": "boolean" + }, + "is_weekend": { + "type": "boolean" + } + } + }, "LOAType": { "description": "The Line of accounting (TAC/SAC) type that will be used for the shipment", "type": "string", @@ -13280,6 +13365,71 @@ func init() { } } }, + "/calendar/{countryCode}/is-weekend-holiday/{date}": { + "get": { + "description": "Utility API to determine if input date falls on weekend and/or holiday.\n", + "produces": [ + "application/json" + ], + "tags": [ + "calendar" + ], + "summary": "Validate move date selection", + "operationId": "isDateWeekendHoliday", + "parameters": [ + { + "enum": [ + "US" + ], + "type": "string", + "description": "country code for context of date", + "name": "countryCode", + "in": "path", + "required": true + }, + { + "type": "string", + "format": "date", + "description": "input date to determine if weekend/holiday for given country.", + "name": "date", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "Successfully determine if given date is weekend and/or holiday for given country.", + "schema": { + "$ref": "#/definitions/IsDateWeekendHolidayInfo" + } + }, + "400": { + "description": "The request payload is invalid", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "401": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "The requested resource wasn't found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + } + }, "/counseling/orders/{orderID}": { "patch": { "description": "All fields sent in this request will be set on the order referenced", @@ -22073,6 +22223,38 @@ func init() { } } }, + "IsDateWeekendHolidayInfo": { + "type": "object", + "required": [ + "country_code", + "country_name", + "date", + "is_weekend", + "is_holiday" + ], + "properties": { + "country_code": { + "type": "string" + }, + "country_name": { + "type": "string" + }, + "date": { + "type": "string", + "format": "date", + "example": "2018-09-25" + }, + "details": { + "type": "string" + }, + "is_holiday": { + "type": "boolean" + }, + "is_weekend": { + "type": "boolean" + } + } + }, "LOAType": { "description": "The Line of accounting (TAC/SAC) type that will be used for the shipment", "type": "string", diff --git a/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday.go b/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday.go new file mode 100644 index 00000000000..2391d9fec61 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package calendar + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime/middleware" +) + +// IsDateWeekendHolidayHandlerFunc turns a function with the right signature into a is date weekend holiday handler +type IsDateWeekendHolidayHandlerFunc func(IsDateWeekendHolidayParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn IsDateWeekendHolidayHandlerFunc) Handle(params IsDateWeekendHolidayParams) middleware.Responder { + return fn(params) +} + +// IsDateWeekendHolidayHandler interface for that can handle valid is date weekend holiday params +type IsDateWeekendHolidayHandler interface { + Handle(IsDateWeekendHolidayParams) middleware.Responder +} + +// NewIsDateWeekendHoliday creates a new http.Handler for the is date weekend holiday operation +func NewIsDateWeekendHoliday(ctx *middleware.Context, handler IsDateWeekendHolidayHandler) *IsDateWeekendHoliday { + return &IsDateWeekendHoliday{Context: ctx, Handler: handler} +} + +/* + IsDateWeekendHoliday swagger:route GET /calendar/{countryCode}/is-weekend-holiday/{date} calendar isDateWeekendHoliday + +# Validate move date selection + +Utility API to determine if input date falls on weekend and/or holiday. +*/ +type IsDateWeekendHoliday struct { + Context *middleware.Context + Handler IsDateWeekendHolidayHandler +} + +func (o *IsDateWeekendHoliday) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewIsDateWeekendHolidayParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_parameters.go b/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_parameters.go new file mode 100644 index 00000000000..bb43341894d --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_parameters.go @@ -0,0 +1,129 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package calendar + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// NewIsDateWeekendHolidayParams creates a new IsDateWeekendHolidayParams object +// +// There are no default values defined in the spec. +func NewIsDateWeekendHolidayParams() IsDateWeekendHolidayParams { + + return IsDateWeekendHolidayParams{} +} + +// IsDateWeekendHolidayParams contains all the bound params for the is date weekend holiday operation +// typically these are obtained from a http.Request +// +// swagger:parameters isDateWeekendHoliday +type IsDateWeekendHolidayParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*country code for context of date + Required: true + In: path + */ + CountryCode string + /*input date to determine if weekend/holiday for given country. + Required: true + In: path + */ + Date strfmt.Date +} + +// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface +// for simple values it will use straight method calls. +// +// To ensure default values, the struct must have been initialized with NewIsDateWeekendHolidayParams() beforehand. +func (o *IsDateWeekendHolidayParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rCountryCode, rhkCountryCode, _ := route.Params.GetOK("countryCode") + if err := o.bindCountryCode(rCountryCode, rhkCountryCode, route.Formats); err != nil { + res = append(res, err) + } + + rDate, rhkDate, _ := route.Params.GetOK("date") + if err := o.bindDate(rDate, rhkDate, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindCountryCode binds and validates parameter CountryCode from path. +func (o *IsDateWeekendHolidayParams) bindCountryCode(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.CountryCode = raw + + if err := o.validateCountryCode(formats); err != nil { + return err + } + + return nil +} + +// validateCountryCode carries on validations for parameter CountryCode +func (o *IsDateWeekendHolidayParams) validateCountryCode(formats strfmt.Registry) error { + + if err := validate.EnumCase("countryCode", "path", o.CountryCode, []interface{}{"US"}, true); err != nil { + return err + } + + return nil +} + +// bindDate binds and validates parameter Date from path. +func (o *IsDateWeekendHolidayParams) bindDate(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + + // Format: date + value, err := formats.Parse("date", raw) + if err != nil { + return errors.InvalidType("date", "path", "strfmt.Date", raw) + } + o.Date = *(value.(*strfmt.Date)) + + if err := o.validateDate(formats); err != nil { + return err + } + + return nil +} + +// validateDate carries on validations for parameter Date +func (o *IsDateWeekendHolidayParams) validateDate(formats strfmt.Registry) error { + + if err := validate.FormatOf("date", "path", "date", o.Date.String(), formats); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_responses.go b/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_responses.go new file mode 100644 index 00000000000..d23e89dea9c --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_responses.go @@ -0,0 +1,239 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package calendar + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// IsDateWeekendHolidayOKCode is the HTTP code returned for type IsDateWeekendHolidayOK +const IsDateWeekendHolidayOKCode int = 200 + +/* +IsDateWeekendHolidayOK Successfully determine if given date is weekend and/or holiday for given country. + +swagger:response isDateWeekendHolidayOK +*/ +type IsDateWeekendHolidayOK struct { + + /* + In: Body + */ + Payload *ghcmessages.IsDateWeekendHolidayInfo `json:"body,omitempty"` +} + +// NewIsDateWeekendHolidayOK creates IsDateWeekendHolidayOK with default headers values +func NewIsDateWeekendHolidayOK() *IsDateWeekendHolidayOK { + + return &IsDateWeekendHolidayOK{} +} + +// WithPayload adds the payload to the is date weekend holiday o k response +func (o *IsDateWeekendHolidayOK) WithPayload(payload *ghcmessages.IsDateWeekendHolidayInfo) *IsDateWeekendHolidayOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the is date weekend holiday o k response +func (o *IsDateWeekendHolidayOK) SetPayload(payload *ghcmessages.IsDateWeekendHolidayInfo) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *IsDateWeekendHolidayOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(200) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// IsDateWeekendHolidayBadRequestCode is the HTTP code returned for type IsDateWeekendHolidayBadRequest +const IsDateWeekendHolidayBadRequestCode int = 400 + +/* +IsDateWeekendHolidayBadRequest The request payload is invalid + +swagger:response isDateWeekendHolidayBadRequest +*/ +type IsDateWeekendHolidayBadRequest struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewIsDateWeekendHolidayBadRequest creates IsDateWeekendHolidayBadRequest with default headers values +func NewIsDateWeekendHolidayBadRequest() *IsDateWeekendHolidayBadRequest { + + return &IsDateWeekendHolidayBadRequest{} +} + +// WithPayload adds the payload to the is date weekend holiday bad request response +func (o *IsDateWeekendHolidayBadRequest) WithPayload(payload *ghcmessages.Error) *IsDateWeekendHolidayBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the is date weekend holiday bad request response +func (o *IsDateWeekendHolidayBadRequest) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *IsDateWeekendHolidayBadRequest) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(400) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// IsDateWeekendHolidayUnauthorizedCode is the HTTP code returned for type IsDateWeekendHolidayUnauthorized +const IsDateWeekendHolidayUnauthorizedCode int = 401 + +/* +IsDateWeekendHolidayUnauthorized The request was denied + +swagger:response isDateWeekendHolidayUnauthorized +*/ +type IsDateWeekendHolidayUnauthorized struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewIsDateWeekendHolidayUnauthorized creates IsDateWeekendHolidayUnauthorized with default headers values +func NewIsDateWeekendHolidayUnauthorized() *IsDateWeekendHolidayUnauthorized { + + return &IsDateWeekendHolidayUnauthorized{} +} + +// WithPayload adds the payload to the is date weekend holiday unauthorized response +func (o *IsDateWeekendHolidayUnauthorized) WithPayload(payload *ghcmessages.Error) *IsDateWeekendHolidayUnauthorized { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the is date weekend holiday unauthorized response +func (o *IsDateWeekendHolidayUnauthorized) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *IsDateWeekendHolidayUnauthorized) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(401) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// IsDateWeekendHolidayNotFoundCode is the HTTP code returned for type IsDateWeekendHolidayNotFound +const IsDateWeekendHolidayNotFoundCode int = 404 + +/* +IsDateWeekendHolidayNotFound The requested resource wasn't found + +swagger:response isDateWeekendHolidayNotFound +*/ +type IsDateWeekendHolidayNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewIsDateWeekendHolidayNotFound creates IsDateWeekendHolidayNotFound with default headers values +func NewIsDateWeekendHolidayNotFound() *IsDateWeekendHolidayNotFound { + + return &IsDateWeekendHolidayNotFound{} +} + +// WithPayload adds the payload to the is date weekend holiday not found response +func (o *IsDateWeekendHolidayNotFound) WithPayload(payload *ghcmessages.Error) *IsDateWeekendHolidayNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the is date weekend holiday not found response +func (o *IsDateWeekendHolidayNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *IsDateWeekendHolidayNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// IsDateWeekendHolidayInternalServerErrorCode is the HTTP code returned for type IsDateWeekendHolidayInternalServerError +const IsDateWeekendHolidayInternalServerErrorCode int = 500 + +/* +IsDateWeekendHolidayInternalServerError A server error occurred + +swagger:response isDateWeekendHolidayInternalServerError +*/ +type IsDateWeekendHolidayInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewIsDateWeekendHolidayInternalServerError creates IsDateWeekendHolidayInternalServerError with default headers values +func NewIsDateWeekendHolidayInternalServerError() *IsDateWeekendHolidayInternalServerError { + + return &IsDateWeekendHolidayInternalServerError{} +} + +// WithPayload adds the payload to the is date weekend holiday internal server error response +func (o *IsDateWeekendHolidayInternalServerError) WithPayload(payload *ghcmessages.Error) *IsDateWeekendHolidayInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the is date weekend holiday internal server error response +func (o *IsDateWeekendHolidayInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *IsDateWeekendHolidayInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_urlbuilder.go new file mode 100644 index 00000000000..1b8e4b91497 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/calendar/is_date_weekend_holiday_urlbuilder.go @@ -0,0 +1,109 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package calendar + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the generate command + +import ( + "errors" + "net/url" + golangswaggerpaths "path" + "strings" + + "github.com/go-openapi/strfmt" +) + +// IsDateWeekendHolidayURL generates an URL for the is date weekend holiday operation +type IsDateWeekendHolidayURL struct { + CountryCode string + Date strfmt.Date + + _basePath string + // avoid unkeyed usage + _ struct{} +} + +// WithBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *IsDateWeekendHolidayURL) WithBasePath(bp string) *IsDateWeekendHolidayURL { + o.SetBasePath(bp) + return o +} + +// SetBasePath sets the base path for this url builder, only required when it's different from the +// base path specified in the swagger spec. +// When the value of the base path is an empty string +func (o *IsDateWeekendHolidayURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *IsDateWeekendHolidayURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/calendar/{countryCode}/is-weekend-holiday/{date}" + + countryCode := o.CountryCode + if countryCode != "" { + _path = strings.Replace(_path, "{countryCode}", countryCode, -1) + } else { + return nil, errors.New("countryCode is required on IsDateWeekendHolidayURL") + } + + date := o.Date.String() + if date != "" { + _path = strings.Replace(_path, "{date}", date, -1) + } else { + return nil, errors.New("date is required on IsDateWeekendHolidayURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *IsDateWeekendHolidayURL) Must(u *url.URL, err error) *url.URL { + if err != nil { + panic(err) + } + if u == nil { + panic("url can't be nil") + } + return u +} + +// String returns the string representation of the path with query string +func (o *IsDateWeekendHolidayURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *IsDateWeekendHolidayURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on IsDateWeekendHolidayURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on IsDateWeekendHolidayURL") + } + + base, err := o.Build() + if err != nil { + return nil, err + } + + base.Scheme = scheme + base.Host = host + return base, nil +} + +// StringFull returns the string representation of a complete url +func (o *IsDateWeekendHolidayURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index f5368032656..8e471e168fa 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -20,6 +20,7 @@ import ( "github.com/go-openapi/swag" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/application_parameters" + "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/calendar" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/customer" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/customer_support_remarks" "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/evaluation_reports" @@ -230,6 +231,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { TransportationOfficeGetTransportationOfficesOpenHandler: transportation_office.GetTransportationOfficesOpenHandlerFunc(func(params transportation_office.GetTransportationOfficesOpenParams) middleware.Responder { return middleware.NotImplemented("operation transportation_office.GetTransportationOfficesOpen has not yet been implemented") }), + CalendarIsDateWeekendHolidayHandler: calendar.IsDateWeekendHolidayHandlerFunc(func(params calendar.IsDateWeekendHolidayParams) middleware.Responder { + return middleware.NotImplemented("operation calendar.IsDateWeekendHoliday has not yet been implemented") + }), MtoServiceItemListMTOServiceItemsHandler: mto_service_item.ListMTOServiceItemsHandlerFunc(func(params mto_service_item.ListMTOServiceItemsParams) middleware.Responder { return middleware.NotImplemented("operation mto_service_item.ListMTOServiceItems has not yet been implemented") }), @@ -504,6 +508,8 @@ type MymoveAPI struct { TransportationOfficeGetTransportationOfficesGBLOCsHandler transportation_office.GetTransportationOfficesGBLOCsHandler // TransportationOfficeGetTransportationOfficesOpenHandler sets the operation handler for the get transportation offices open operation TransportationOfficeGetTransportationOfficesOpenHandler transportation_office.GetTransportationOfficesOpenHandler + // CalendarIsDateWeekendHolidayHandler sets the operation handler for the is date weekend holiday operation + CalendarIsDateWeekendHolidayHandler calendar.IsDateWeekendHolidayHandler // MtoServiceItemListMTOServiceItemsHandler sets the operation handler for the list m t o service items operation MtoServiceItemListMTOServiceItemsHandler mto_service_item.ListMTOServiceItemsHandler // MtoShipmentListMTOShipmentsHandler sets the operation handler for the list m t o shipments operation @@ -829,6 +835,9 @@ func (o *MymoveAPI) Validate() error { if o.TransportationOfficeGetTransportationOfficesOpenHandler == nil { unregistered = append(unregistered, "transportation_office.GetTransportationOfficesOpenHandler") } + if o.CalendarIsDateWeekendHolidayHandler == nil { + unregistered = append(unregistered, "calendar.IsDateWeekendHolidayHandler") + } if o.MtoServiceItemListMTOServiceItemsHandler == nil { unregistered = append(unregistered, "mto_service_item.ListMTOServiceItemsHandler") } @@ -1260,6 +1269,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) } + o.handlers["GET"]["/calendar/{countryCode}/is-weekend-holiday/{date}"] = calendar.NewIsDateWeekendHoliday(o.context, o.CalendarIsDateWeekendHolidayHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } o.handlers["GET"]["/move_task_orders/{moveTaskOrderID}/mto_service_items"] = mto_service_item.NewListMTOServiceItems(o.context, o.MtoServiceItemListMTOServiceItemsHandler) if o.handlers["GET"] == nil { o.handlers["GET"] = make(map[string]http.Handler) diff --git a/pkg/gen/ghcmessages/is_date_weekend_holiday_info.go b/pkg/gen/ghcmessages/is_date_weekend_holiday_info.go new file mode 100644 index 00000000000..46f40a747e0 --- /dev/null +++ b/pkg/gen/ghcmessages/is_date_weekend_holiday_info.go @@ -0,0 +1,148 @@ +// 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" + + "github.com/go-openapi/errors" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/swag" + "github.com/go-openapi/validate" +) + +// IsDateWeekendHolidayInfo is date weekend holiday info +// +// swagger:model IsDateWeekendHolidayInfo +type IsDateWeekendHolidayInfo struct { + + // country code + // Required: true + CountryCode *string `json:"country_code"` + + // country name + // Required: true + CountryName *string `json:"country_name"` + + // date + // Example: 2018-09-25 + // Required: true + // Format: date + Date *strfmt.Date `json:"date"` + + // details + Details string `json:"details,omitempty"` + + // is holiday + // Required: true + IsHoliday *bool `json:"is_holiday"` + + // is weekend + // Required: true + IsWeekend *bool `json:"is_weekend"` +} + +// Validate validates this is date weekend holiday info +func (m *IsDateWeekendHolidayInfo) Validate(formats strfmt.Registry) error { + var res []error + + if err := m.validateCountryCode(formats); err != nil { + res = append(res, err) + } + + if err := m.validateCountryName(formats); err != nil { + res = append(res, err) + } + + if err := m.validateDate(formats); err != nil { + res = append(res, err) + } + + if err := m.validateIsHoliday(formats); err != nil { + res = append(res, err) + } + + if err := m.validateIsWeekend(formats); err != nil { + res = append(res, err) + } + + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +func (m *IsDateWeekendHolidayInfo) validateCountryCode(formats strfmt.Registry) error { + + if err := validate.Required("country_code", "body", m.CountryCode); err != nil { + return err + } + + return nil +} + +func (m *IsDateWeekendHolidayInfo) validateCountryName(formats strfmt.Registry) error { + + if err := validate.Required("country_name", "body", m.CountryName); err != nil { + return err + } + + return nil +} + +func (m *IsDateWeekendHolidayInfo) validateDate(formats strfmt.Registry) error { + + if err := validate.Required("date", "body", m.Date); err != nil { + return err + } + + if err := validate.FormatOf("date", "body", "date", m.Date.String(), formats); err != nil { + return err + } + + return nil +} + +func (m *IsDateWeekendHolidayInfo) validateIsHoliday(formats strfmt.Registry) error { + + if err := validate.Required("is_holiday", "body", m.IsHoliday); err != nil { + return err + } + + return nil +} + +func (m *IsDateWeekendHolidayInfo) validateIsWeekend(formats strfmt.Registry) error { + + if err := validate.Required("is_weekend", "body", m.IsWeekend); err != nil { + return err + } + + return nil +} + +// ContextValidate validates this is date weekend holiday info based on context it is used +func (m *IsDateWeekendHolidayInfo) ContextValidate(ctx context.Context, formats strfmt.Registry) error { + return nil +} + +// MarshalBinary interface implementation +func (m *IsDateWeekendHolidayInfo) MarshalBinary() ([]byte, error) { + if m == nil { + return nil, nil + } + return swag.WriteJSON(m) +} + +// UnmarshalBinary interface implementation +func (m *IsDateWeekendHolidayInfo) UnmarshalBinary(b []byte) error { + var res IsDateWeekendHolidayInfo + if err := swag.ReadJSON(b, &res); err != nil { + return err + } + *m = res + return nil +} diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index b5c77853288..4e41be7556f 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -11,6 +11,7 @@ import ( paperwork "github.com/transcom/mymove/pkg/paperwork" paymentrequesthelper "github.com/transcom/mymove/pkg/payment_request" "github.com/transcom/mymove/pkg/services/address" + dateservice "github.com/transcom/mymove/pkg/services/calendar" customerserviceremarks "github.com/transcom/mymove/pkg/services/customer_support_remarks" evaluationreport "github.com/transcom/mymove/pkg/services/evaluation_report" "github.com/transcom/mymove/pkg/services/fetch" @@ -662,5 +663,8 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { order.NewOrderUpdater(moveRouter), } + dateSelectionChecker := dateservice.NewDateSelectionChecker() + ghcAPI.CalendarIsDateWeekendHolidayHandler = IsDateWeekendHolidayHandler{handlerConfig, dateSelectionChecker} + return ghcAPI } diff --git a/pkg/handlers/ghcapi/calendar.go b/pkg/handlers/ghcapi/calendar.go new file mode 100644 index 00000000000..aec483886c8 --- /dev/null +++ b/pkg/handlers/ghcapi/calendar.go @@ -0,0 +1,36 @@ +package ghcapi + +import ( + "time" + + "github.com/go-openapi/runtime/middleware" + + "github.com/transcom/mymove/pkg/appcontext" + calendarop "github.com/transcom/mymove/pkg/gen/ghcapi/ghcoperations/calendar" + "github.com/transcom/mymove/pkg/gen/ghcmessages" + "github.com/transcom/mymove/pkg/handlers" + "github.com/transcom/mymove/pkg/services" +) + +type IsDateWeekendHolidayHandler struct { + handlers.HandlerConfig + services.DateSelectionChecker +} + +func (h IsDateWeekendHolidayHandler) Handle(params calendarop.IsDateWeekendHolidayParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + date := time.Time(params.Date) + info, err := h.DateSelectionChecker.IsDateWeekendHoliday(appCtx, params.CountryCode, date) + if err != nil { + return calendarop.NewIsDateWeekendHolidayInternalServerError().WithPayload(nil), nil + } + var isDateWeekendHolidayInfo ghcmessages.IsDateWeekendHolidayInfo + isDateWeekendHolidayInfo.CountryCode = &info.CountryCode + isDateWeekendHolidayInfo.CountryName = &info.CountryName + isDateWeekendHolidayInfo.Date = handlers.FmtDate(info.Date) + isDateWeekendHolidayInfo.IsWeekend = &info.IsWeekend + isDateWeekendHolidayInfo.IsHoliday = &info.IsHoliday + return calendarop.NewIsDateWeekendHolidayOK().WithPayload(&isDateWeekendHolidayInfo), nil + }) +} diff --git a/src/components/Office/ShipmentForm/ShipmentForm.jsx b/src/components/Office/ShipmentForm/ShipmentForm.jsx index eda2ecbd1ad..2a0a97cbb89 100644 --- a/src/components/Office/ShipmentForm/ShipmentForm.jsx +++ b/src/components/Office/ShipmentForm/ShipmentForm.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { arrayOf, bool, func, number, shape, string, oneOf } from 'prop-types'; import { Field, Formik } from 'formik'; import { generatePath, useNavigate, useParams } from 'react-router-dom'; @@ -39,7 +39,12 @@ import { MOVES, MTO_SHIPMENTS } from 'constants/queryKeys'; import { servicesCounselingRoutes, tooRoutes } from 'constants/routes'; import { ADDRESS_UPDATE_STATUS, shipmentDestinationTypes } from 'constants/shipments'; import { officeRoles, roleTypes } from 'constants/userRoles'; -import { deleteShipment, reviewShipmentAddressUpdate, updateMoveCloseoutOffice } from 'services/ghcApi'; +import { + deleteShipment, + reviewShipmentAddressUpdate, + updateMoveCloseoutOffice, + dateSelectionIsWeekendHoliday, +} from 'services/ghcApi'; import { SHIPMENT_OPTIONS } from 'shared/constants'; import formStyles from 'styles/form.module.scss'; import { AccountingCodesShape } from 'types/accountingCodes'; @@ -54,6 +59,8 @@ import { } from 'utils/formatMtoShipment'; import { formatWeight, dropdownInputOptions } from 'utils/formatters'; import { validateDate } from 'utils/validation'; +import { dateSelectionWeekendHolidayCheck } from 'shared/calendar'; +import { datePickerFormat, formatDate } from 'shared/dates'; const ShipmentForm = (props) => { const { @@ -94,6 +101,12 @@ const ShipmentForm = (props) => { const shipments = mtoShipments; + const [isRequestedPickupDateAlertVisible, setIsRequestedPickupDateAlertVisible] = useState(false); + const [isRequestedDeliveryDateAlertVisible, setIsRequestedDeliveryDateAlertVisible] = useState(false); + const [requestedPickupDateAlertMessage, setRequestedPickupDateAlertMessage] = useState(''); + const [requestedDeliveryDateAlertMessage, setRequestedDeliveryDateAlertMessage] = useState(''); + const DEFAULT_COUNTRY_CODE = 'US'; + const queryClient = useQueryClient(); const { mutate: mutateMTOShipmentStatus } = useMutation(deleteShipment, { onSuccess: (_, variables) => { @@ -176,6 +189,38 @@ const ShipmentForm = (props) => { setIsCancelModalVisible(true); }; + useEffect(() => { + const onErrorHandler = (e) => { + const { response } = e; + setErrorMessage(response?.body?.detail); + }; + dateSelectionWeekendHolidayCheck( + dateSelectionIsWeekendHoliday, + DEFAULT_COUNTRY_CODE, + new Date(mtoShipment.requestedPickupDate), + 'Requested pickup date', + setRequestedPickupDateAlertMessage, + setIsRequestedPickupDateAlertVisible, + onErrorHandler, + ); + }, [mtoShipment.requestedPickupDate]); + + useEffect(() => { + const onErrorHandler = (e) => { + const { response } = e; + setErrorMessage(response?.body?.detail); + }; + dateSelectionWeekendHolidayCheck( + dateSelectionIsWeekendHoliday, + DEFAULT_COUNTRY_CODE, + new Date(mtoShipment.requestedDeliveryDate), + 'Requested delivery date', + setRequestedDeliveryDateAlertMessage, + setIsRequestedDeliveryDateAlertVisible, + onErrorHandler, + ); + }, [mtoShipment.requestedDeliveryDate]); + const successMessageAlertControl = (