From a2bfb1bb663d52a20363cac2c249d53ca835cc65 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Tue, 21 May 2024 13:23:25 +0000 Subject: [PATCH 01/10] basic function working needs lots of cleanup --- pkg/gen/internalapi/configure_mymove.go | 7 + pkg/gen/internalapi/embedded_spec.go | 112 +++++++++ .../moves/upload_additional_documents.go | 58 +++++ .../upload_additional_documents_parameters.go | 131 ++++++++++ .../upload_additional_documents_responses.go | 204 ++++++++++++++++ .../upload_additional_documents_urlbuilder.go | 101 ++++++++ .../internaloperations/mymove_api.go | 12 + pkg/handlers/internalapi/api.go | 5 + pkg/handlers/internalapi/moves.go | 78 ++++++ pkg/models/move.go | 2 + pkg/services/move.go | 12 + .../move/additional_documents_uploader.go | 226 ++++++++++++++++++ pkg/services/order/order_updater.go | 7 +- src/constants/routes.js | 2 + .../AdditionalDocuments.jsx | 133 +++++++++++ src/pages/MyMove/Home/MoveHome.jsx | 20 ++ src/scenes/MyMove/index.jsx | 11 + src/services/internalApi.js | 14 ++ .../definitions/AdditionalDocuments.yaml | 42 ++++ swagger-def/internal.yaml | 76 ++++++ swagger/internal.yaml | 38 +++ 21 files changed, 1289 insertions(+), 2 deletions(-) create mode 100644 pkg/gen/internalapi/internaloperations/moves/upload_additional_documents.go create mode 100644 pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_parameters.go create mode 100644 pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_responses.go create mode 100644 pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_urlbuilder.go create mode 100644 pkg/services/move/additional_documents_uploader.go create mode 100644 src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx create mode 100644 swagger-def/definitions/AdditionalDocuments.yaml diff --git a/pkg/gen/internalapi/configure_mymove.go b/pkg/gen/internalapi/configure_mymove.go index 97339221908..1295e8d8ac8 100644 --- a/pkg/gen/internalapi/configure_mymove.go +++ b/pkg/gen/internalapi/configure_mymove.go @@ -66,6 +66,8 @@ func configureAPI(api *internaloperations.MymoveAPI) http.Handler { // You may change here the memory limit for this multipart form parser. Below is the default (32 MB). // uploads.CreateUploadMaxParseMemory = 32 << 20 // You may change here the memory limit for this multipart form parser. Below is the default (32 MB). + // moves.UploadAdditionalDocumentsMaxParseMemory = 32 << 20 + // You may change here the memory limit for this multipart form parser. Below is the default (32 MB). // orders.UploadAmendedOrdersMaxParseMemory = 32 << 20 if api.OfficeApproveMoveHandler == nil { @@ -378,6 +380,11 @@ func configureAPI(api *internaloperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation ppm.UpdateWeightTicket has not yet been implemented") }) } + if api.MovesUploadAdditionalDocumentsHandler == nil { + api.MovesUploadAdditionalDocumentsHandler = moves.UploadAdditionalDocumentsHandlerFunc(func(params moves.UploadAdditionalDocumentsParams) middleware.Responder { + return middleware.NotImplemented("operation moves.UploadAdditionalDocuments has not yet been implemented") + }) + } if api.OrdersUploadAmendedOrdersHandler == nil { api.OrdersUploadAmendedOrdersHandler = orders.UploadAmendedOrdersHandlerFunc(func(params orders.UploadAmendedOrdersParams) middleware.Responder { return middleware.NotImplemented("operation orders.UploadAmendedOrders has not yet been implemented") diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index 30c2651f574..8bb8a64b102 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -1183,6 +1183,62 @@ func init() { } } }, + "/moves/{moveId}/upload_additional_documents": { + "patch": { + "description": "Patch the amended orders for a given order", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "moves" + ], + "summary": "Patch the amended orders for a given order", + "operationId": "uploadAdditionalDocuments", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "UUID of the order", + "name": "moveId", + "in": "path", + "required": true + }, + { + "type": "file", + "description": "The file to upload.", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "201": { + "description": "created upload", + "schema": { + "$ref": "#/definitions/Upload" + } + }, + "400": { + "description": "invalid request", + "schema": { + "$ref": "#/definitions/InvalidRequestResponsePayload" + } + }, + "403": { + "description": "not authorized" + }, + "404": { + "description": "not found" + }, + "413": { + "description": "payload is too large" + }, + "500": { + "description": "server error" + } + } + } + }, "/moves/{moveId}/weight_ticket": { "post": { "description": "Created a weight ticket document with the given information", @@ -8557,6 +8613,62 @@ func init() { } } }, + "/moves/{moveId}/upload_additional_documents": { + "patch": { + "description": "Patch the amended orders for a given order", + "consumes": [ + "multipart/form-data" + ], + "tags": [ + "moves" + ], + "summary": "Patch the amended orders for a given order", + "operationId": "uploadAdditionalDocuments", + "parameters": [ + { + "type": "string", + "format": "uuid", + "description": "UUID of the order", + "name": "moveId", + "in": "path", + "required": true + }, + { + "type": "file", + "description": "The file to upload.", + "name": "file", + "in": "formData", + "required": true + } + ], + "responses": { + "201": { + "description": "created upload", + "schema": { + "$ref": "#/definitions/Upload" + } + }, + "400": { + "description": "invalid request", + "schema": { + "$ref": "#/definitions/InvalidRequestResponsePayload" + } + }, + "403": { + "description": "not authorized" + }, + "404": { + "description": "not found" + }, + "413": { + "description": "payload is too large" + }, + "500": { + "description": "server error" + } + } + } + }, "/moves/{moveId}/weight_ticket": { "post": { "description": "Created a weight ticket document with the given information", diff --git a/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents.go b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents.go new file mode 100644 index 00000000000..0504b54a57a --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package moves + +// 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" +) + +// UploadAdditionalDocumentsHandlerFunc turns a function with the right signature into a upload additional documents handler +type UploadAdditionalDocumentsHandlerFunc func(UploadAdditionalDocumentsParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn UploadAdditionalDocumentsHandlerFunc) Handle(params UploadAdditionalDocumentsParams) middleware.Responder { + return fn(params) +} + +// UploadAdditionalDocumentsHandler interface for that can handle valid upload additional documents params +type UploadAdditionalDocumentsHandler interface { + Handle(UploadAdditionalDocumentsParams) middleware.Responder +} + +// NewUploadAdditionalDocuments creates a new http.Handler for the upload additional documents operation +func NewUploadAdditionalDocuments(ctx *middleware.Context, handler UploadAdditionalDocumentsHandler) *UploadAdditionalDocuments { + return &UploadAdditionalDocuments{Context: ctx, Handler: handler} +} + +/* + UploadAdditionalDocuments swagger:route PATCH /moves/{moveId}/upload_additional_documents moves uploadAdditionalDocuments + +# Patch the amended orders for a given order + +Patch the amended orders for a given order +*/ +type UploadAdditionalDocuments struct { + Context *middleware.Context + Handler UploadAdditionalDocumentsHandler +} + +func (o *UploadAdditionalDocuments) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewUploadAdditionalDocumentsParams() + 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/moves/upload_additional_documents_parameters.go b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_parameters.go new file mode 100644 index 00000000000..baf9cfbacc1 --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_parameters.go @@ -0,0 +1,131 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package moves + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "mime/multipart" + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" + "github.com/go-openapi/validate" +) + +// UploadAdditionalDocumentsMaxParseMemory sets the maximum size in bytes for +// the multipart form parser for this operation. +// +// The default value is 32 MB. +// The multipart parser stores up to this + 10MB. +var UploadAdditionalDocumentsMaxParseMemory int64 = 32 << 20 + +// NewUploadAdditionalDocumentsParams creates a new UploadAdditionalDocumentsParams object +// +// There are no default values defined in the spec. +func NewUploadAdditionalDocumentsParams() UploadAdditionalDocumentsParams { + + return UploadAdditionalDocumentsParams{} +} + +// UploadAdditionalDocumentsParams contains all the bound params for the upload additional documents operation +// typically these are obtained from a http.Request +// +// swagger:parameters uploadAdditionalDocuments +type UploadAdditionalDocumentsParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*The file to upload. + Required: true + In: formData + */ + File io.ReadCloser + /*UUID of the order + Required: true + In: path + */ + MoveID strfmt.UUID +} + +// 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 NewUploadAdditionalDocumentsParams() beforehand. +func (o *UploadAdditionalDocumentsParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + if err := r.ParseMultipartForm(UploadAdditionalDocumentsMaxParseMemory); err != nil { + if err != http.ErrNotMultipart { + return errors.New(400, "%v", err) + } else if err := r.ParseForm(); err != nil { + return errors.New(400, "%v", err) + } + } + + file, fileHeader, err := r.FormFile("file") + if err != nil { + res = append(res, errors.New(400, "reading file %q failed: %v", "file", err)) + } else if err := o.bindFile(file, fileHeader); err != nil { + // Required: true + res = append(res, err) + } else { + o.File = &runtime.File{Data: file, Header: fileHeader} + } + + rMoveID, rhkMoveID, _ := route.Params.GetOK("moveId") + if err := o.bindMoveID(rMoveID, rhkMoveID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindFile binds file parameter File. +// +// The only supported validations on files are MinLength and MaxLength +func (o *UploadAdditionalDocumentsParams) bindFile(file multipart.File, header *multipart.FileHeader) error { + return nil +} + +// bindMoveID binds and validates parameter MoveID from path. +func (o *UploadAdditionalDocumentsParams) bindMoveID(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: uuid + value, err := formats.Parse("uuid", raw) + if err != nil { + return errors.InvalidType("moveId", "path", "strfmt.UUID", raw) + } + o.MoveID = *(value.(*strfmt.UUID)) + + if err := o.validateMoveID(formats); err != nil { + return err + } + + return nil +} + +// validateMoveID carries on validations for parameter MoveID +func (o *UploadAdditionalDocumentsParams) validateMoveID(formats strfmt.Registry) error { + + if err := validate.FormatOf("moveId", "path", "uuid", o.MoveID.String(), formats); err != nil { + return err + } + return nil +} diff --git a/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_responses.go b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_responses.go new file mode 100644 index 00000000000..368ed1ebcfb --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_responses.go @@ -0,0 +1,204 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package moves + +// 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" +) + +// UploadAdditionalDocumentsCreatedCode is the HTTP code returned for type UploadAdditionalDocumentsCreated +const UploadAdditionalDocumentsCreatedCode int = 201 + +/* +UploadAdditionalDocumentsCreated created upload + +swagger:response uploadAdditionalDocumentsCreated +*/ +type UploadAdditionalDocumentsCreated struct { + + /* + In: Body + */ + Payload *internalmessages.Upload `json:"body,omitempty"` +} + +// NewUploadAdditionalDocumentsCreated creates UploadAdditionalDocumentsCreated with default headers values +func NewUploadAdditionalDocumentsCreated() *UploadAdditionalDocumentsCreated { + + return &UploadAdditionalDocumentsCreated{} +} + +// WithPayload adds the payload to the upload additional documents created response +func (o *UploadAdditionalDocumentsCreated) WithPayload(payload *internalmessages.Upload) *UploadAdditionalDocumentsCreated { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the upload additional documents created response +func (o *UploadAdditionalDocumentsCreated) SetPayload(payload *internalmessages.Upload) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *UploadAdditionalDocumentsCreated) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(201) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// UploadAdditionalDocumentsBadRequestCode is the HTTP code returned for type UploadAdditionalDocumentsBadRequest +const UploadAdditionalDocumentsBadRequestCode int = 400 + +/* +UploadAdditionalDocumentsBadRequest invalid request + +swagger:response uploadAdditionalDocumentsBadRequest +*/ +type UploadAdditionalDocumentsBadRequest struct { + + /* + In: Body + */ + Payload *internalmessages.InvalidRequestResponsePayload `json:"body,omitempty"` +} + +// NewUploadAdditionalDocumentsBadRequest creates UploadAdditionalDocumentsBadRequest with default headers values +func NewUploadAdditionalDocumentsBadRequest() *UploadAdditionalDocumentsBadRequest { + + return &UploadAdditionalDocumentsBadRequest{} +} + +// WithPayload adds the payload to the upload additional documents bad request response +func (o *UploadAdditionalDocumentsBadRequest) WithPayload(payload *internalmessages.InvalidRequestResponsePayload) *UploadAdditionalDocumentsBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the upload additional documents bad request response +func (o *UploadAdditionalDocumentsBadRequest) SetPayload(payload *internalmessages.InvalidRequestResponsePayload) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *UploadAdditionalDocumentsBadRequest) 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 + } + } +} + +// UploadAdditionalDocumentsForbiddenCode is the HTTP code returned for type UploadAdditionalDocumentsForbidden +const UploadAdditionalDocumentsForbiddenCode int = 403 + +/* +UploadAdditionalDocumentsForbidden not authorized + +swagger:response uploadAdditionalDocumentsForbidden +*/ +type UploadAdditionalDocumentsForbidden struct { +} + +// NewUploadAdditionalDocumentsForbidden creates UploadAdditionalDocumentsForbidden with default headers values +func NewUploadAdditionalDocumentsForbidden() *UploadAdditionalDocumentsForbidden { + + return &UploadAdditionalDocumentsForbidden{} +} + +// WriteResponse to the client +func (o *UploadAdditionalDocumentsForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(403) +} + +// UploadAdditionalDocumentsNotFoundCode is the HTTP code returned for type UploadAdditionalDocumentsNotFound +const UploadAdditionalDocumentsNotFoundCode int = 404 + +/* +UploadAdditionalDocumentsNotFound not found + +swagger:response uploadAdditionalDocumentsNotFound +*/ +type UploadAdditionalDocumentsNotFound struct { +} + +// NewUploadAdditionalDocumentsNotFound creates UploadAdditionalDocumentsNotFound with default headers values +func NewUploadAdditionalDocumentsNotFound() *UploadAdditionalDocumentsNotFound { + + return &UploadAdditionalDocumentsNotFound{} +} + +// WriteResponse to the client +func (o *UploadAdditionalDocumentsNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(404) +} + +// UploadAdditionalDocumentsRequestEntityTooLargeCode is the HTTP code returned for type UploadAdditionalDocumentsRequestEntityTooLarge +const UploadAdditionalDocumentsRequestEntityTooLargeCode int = 413 + +/* +UploadAdditionalDocumentsRequestEntityTooLarge payload is too large + +swagger:response uploadAdditionalDocumentsRequestEntityTooLarge +*/ +type UploadAdditionalDocumentsRequestEntityTooLarge struct { +} + +// NewUploadAdditionalDocumentsRequestEntityTooLarge creates UploadAdditionalDocumentsRequestEntityTooLarge with default headers values +func NewUploadAdditionalDocumentsRequestEntityTooLarge() *UploadAdditionalDocumentsRequestEntityTooLarge { + + return &UploadAdditionalDocumentsRequestEntityTooLarge{} +} + +// WriteResponse to the client +func (o *UploadAdditionalDocumentsRequestEntityTooLarge) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(413) +} + +// UploadAdditionalDocumentsInternalServerErrorCode is the HTTP code returned for type UploadAdditionalDocumentsInternalServerError +const UploadAdditionalDocumentsInternalServerErrorCode int = 500 + +/* +UploadAdditionalDocumentsInternalServerError server error + +swagger:response uploadAdditionalDocumentsInternalServerError +*/ +type UploadAdditionalDocumentsInternalServerError struct { +} + +// NewUploadAdditionalDocumentsInternalServerError creates UploadAdditionalDocumentsInternalServerError with default headers values +func NewUploadAdditionalDocumentsInternalServerError() *UploadAdditionalDocumentsInternalServerError { + + return &UploadAdditionalDocumentsInternalServerError{} +} + +// WriteResponse to the client +func (o *UploadAdditionalDocumentsInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.Header().Del(runtime.HeaderContentType) //Remove Content-Type on empty responses + + rw.WriteHeader(500) +} diff --git a/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_urlbuilder.go b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_urlbuilder.go new file mode 100644 index 00000000000..bef9f4491c8 --- /dev/null +++ b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_urlbuilder.go @@ -0,0 +1,101 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package moves + +// 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" +) + +// UploadAdditionalDocumentsURL generates an URL for the upload additional documents operation +type UploadAdditionalDocumentsURL struct { + MoveID strfmt.UUID + + _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 *UploadAdditionalDocumentsURL) WithBasePath(bp string) *UploadAdditionalDocumentsURL { + 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 *UploadAdditionalDocumentsURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *UploadAdditionalDocumentsURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/moves/{moveId}/upload_additional_documents" + + moveID := o.MoveID.String() + if moveID != "" { + _path = strings.Replace(_path, "{moveId}", moveID, -1) + } else { + return nil, errors.New("moveId is required on UploadAdditionalDocumentsURL") + } + + _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 *UploadAdditionalDocumentsURL) 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 *UploadAdditionalDocumentsURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *UploadAdditionalDocumentsURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on UploadAdditionalDocumentsURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on UploadAdditionalDocumentsURL") + } + + 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 *UploadAdditionalDocumentsURL) 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 df4ca47a53f..5d4e294fac7 100644 --- a/pkg/gen/internalapi/internaloperations/mymove_api.go +++ b/pkg/gen/internalapi/internaloperations/mymove_api.go @@ -253,6 +253,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { PpmUpdateWeightTicketHandler: ppm.UpdateWeightTicketHandlerFunc(func(params ppm.UpdateWeightTicketParams) middleware.Responder { return middleware.NotImplemented("operation ppm.UpdateWeightTicket has not yet been implemented") }), + MovesUploadAdditionalDocumentsHandler: moves.UploadAdditionalDocumentsHandlerFunc(func(params moves.UploadAdditionalDocumentsParams) middleware.Responder { + return middleware.NotImplemented("operation moves.UploadAdditionalDocuments has not yet been implemented") + }), OrdersUploadAmendedOrdersHandler: orders.UploadAmendedOrdersHandlerFunc(func(params orders.UploadAmendedOrdersParams) middleware.Responder { return middleware.NotImplemented("operation orders.UploadAmendedOrders has not yet been implemented") }), @@ -436,6 +439,8 @@ type MymoveAPI struct { BackupContactsUpdateServiceMemberBackupContactHandler backup_contacts.UpdateServiceMemberBackupContactHandler // PpmUpdateWeightTicketHandler sets the operation handler for the update weight ticket operation PpmUpdateWeightTicketHandler ppm.UpdateWeightTicketHandler + // MovesUploadAdditionalDocumentsHandler sets the operation handler for the upload additional documents operation + MovesUploadAdditionalDocumentsHandler moves.UploadAdditionalDocumentsHandler // OrdersUploadAmendedOrdersHandler sets the operation handler for the upload amended orders operation OrdersUploadAmendedOrdersHandler orders.UploadAmendedOrdersHandler // ApplicationParametersValidateHandler sets the operation handler for the validate operation @@ -713,6 +718,9 @@ func (o *MymoveAPI) Validate() error { if o.PpmUpdateWeightTicketHandler == nil { unregistered = append(unregistered, "ppm.UpdateWeightTicketHandler") } + if o.MovesUploadAdditionalDocumentsHandler == nil { + unregistered = append(unregistered, "moves.UploadAdditionalDocumentsHandler") + } if o.OrdersUploadAmendedOrdersHandler == nil { unregistered = append(unregistered, "orders.UploadAmendedOrdersHandler") } @@ -1068,6 +1076,10 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["PATCH"] == nil { o.handlers["PATCH"] = make(map[string]http.Handler) } + o.handlers["PATCH"]["/moves/{moveId}/upload_additional_documents"] = moves.NewUploadAdditionalDocuments(o.context, o.MovesUploadAdditionalDocumentsHandler) + if o.handlers["PATCH"] == nil { + o.handlers["PATCH"] = make(map[string]http.Handler) + } o.handlers["PATCH"]["/orders/{ordersId}/upload_amended_orders"] = orders.NewUploadAmendedOrders(o.context, o.OrdersUploadAmendedOrdersHandler) if o.handlers["POST"] == nil { o.handlers["POST"] = make(map[string]http.Handler) diff --git a/pkg/handlers/internalapi/api.go b/pkg/handlers/internalapi/api.go index 0c6cd89f0d3..981585e1ff7 100644 --- a/pkg/handlers/internalapi/api.go +++ b/pkg/handlers/internalapi/api.go @@ -53,6 +53,7 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI builder := query.NewQueryBuilder() fetcher := fetch.NewFetcher(builder) moveRouter := move.NewMoveRouter() + uploadCreator := upload.NewUploadCreator(handlerConfig.FileStorer()) SSWPPMComputer := shipmentsummaryworksheet.NewSSWPPMComputer() userUploader, err := uploader.NewUserUploader(handlerConfig.FileStorer(), uploader.MaxCustomerUserUploadFileSizeLimit) @@ -134,6 +135,10 @@ func NewInternalAPI(handlerConfig handlers.HandlerConfig) *internalops.MymoveAPI handlerConfig, moveRouter, } + internalAPI.MovesUploadAdditionalDocumentsHandler = UploadAdditionalDocumentsHandler{ + handlerConfig, + move.NewMoveAdditionalDocumentsUploader(uploadCreator), + } internalAPI.OktaProfileShowOktaInfoHandler = GetOktaProfileHandler{handlerConfig} internalAPI.OktaProfileUpdateOktaInfoHandler = UpdateOktaProfileHandler{handlerConfig} diff --git a/pkg/handlers/internalapi/moves.go b/pkg/handlers/internalapi/moves.go index de0692b3cc7..1ea59699139 100644 --- a/pkg/handlers/internalapi/moves.go +++ b/pkg/handlers/internalapi/moves.go @@ -3,7 +3,9 @@ package internalapi import ( "time" + "github.com/go-openapi/runtime" "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" "github.com/gobuffalo/validate/v3" "github.com/gofrs/uuid" "github.com/pkg/errors" @@ -20,6 +22,7 @@ import ( "github.com/transcom/mymove/pkg/notifications" "github.com/transcom/mymove/pkg/services" "github.com/transcom/mymove/pkg/storage" + "github.com/transcom/mymove/pkg/uploader" ) func payloadForMoveModel(storer storage.FileStorer, order models.Order, move models.Move) (*internalmessages.MovePayload, error) { @@ -365,3 +368,78 @@ func (h GetAllMovesHandler) Handle(params moveop.GetAllMovesParams) middleware.R return moveop.NewGetAllMovesOK().WithPayload(payloadForMovesList(h.FileStorer(), previousMovesList, currentMovesList, movesList)), nil }) } + +type UploadAdditionalDocumentsHandler struct { + handlers.HandlerConfig + uploader services.MoveAdditionalDocumentsUploader +} + +func (h UploadAdditionalDocumentsHandler) Handle(params moveop.UploadAdditionalDocumentsParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + + file, ok := params.File.(*runtime.File) + if !ok { + errMsg := "This should always be a runtime.File, something has changed in go-swagger." + + appCtx.Logger().Error(errMsg) + + return moveop.NewUploadAdditionalDocumentsInternalServerError(), nil + } + + appCtx.Logger().Info( + "File uploader and size", + zap.String("userID", appCtx.Session().UserID.String()), + zap.String("serviceMemberID", appCtx.Session().ServiceMemberID.String()), + zap.String("officeUserID", appCtx.Session().OfficeUserID.String()), + zap.String("AdminUserID", appCtx.Session().AdminUserID.String()), + zap.Int64("size", file.Header.Size), + ) + + moveID, err := uuid.FromString(params.MoveID.String()) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + upload, url, verrs, err := h.uploader.CreateAdditionalDocumentsUpload(appCtx, appCtx.Session().UserID, moveID, file.Data, file.Header.Filename, h.FileStorer()) + + if verrs.HasAny() || err != nil { + switch err.(type) { + case uploader.ErrTooLarge: + return moveop.NewUploadAdditionalDocumentsRequestEntityTooLarge(), err + case uploader.ErrFile: + return moveop.NewUploadAdditionalDocumentsInternalServerError(), err + case uploader.ErrFailedToInitUploader: + return moveop.NewUploadAdditionalDocumentsInternalServerError(), err + case apperror.NotFoundError: + return moveop.NewUploadAdditionalDocumentsNotFound(), err + default: + return handlers.ResponseForVErrors(appCtx.Logger(), verrs, err), err + } + } + + uploadPayload, err := payloadForUploadModelFromAdditionalDocumentsUpload(h.FileStorer(), upload, url) + if err != nil { + return handlers.ResponseForError(appCtx.Logger(), err), err + } + return moveop.NewUploadAdditionalDocumentsCreated().WithPayload(uploadPayload), nil + }) +} + +func payloadForUploadModelFromAdditionalDocumentsUpload(storer storage.FileStorer, upload models.Upload, url string) (*internalmessages.Upload, error) { + uploadPayload := &internalmessages.Upload{ + ID: handlers.FmtUUIDValue(upload.ID), + Filename: upload.Filename, + ContentType: upload.ContentType, + URL: strfmt.URI(url), + Bytes: upload.Bytes, + CreatedAt: strfmt.DateTime(upload.CreatedAt), + UpdatedAt: strfmt.DateTime(upload.UpdatedAt), + } + tags, err := storer.Tags(upload.StorageKey) + if err != nil || len(tags) == 0 { + uploadPayload.Status = "PROCESSING" + } else { + uploadPayload.Status = tags["av-status"] + } + return uploadPayload, nil +} diff --git a/pkg/models/move.go b/pkg/models/move.go index 385ce5c52c6..52e56bd234c 100644 --- a/pkg/models/move.go +++ b/pkg/models/move.go @@ -89,6 +89,8 @@ type Move struct { LockedByOfficeUserID *uuid.UUID `json:"locked_by" db:"locked_by"` LockedByOfficeUser *OfficeUser `belongs_to:"office_users" fk_id:"locked_by"` LockExpiresAt *time.Time `json:"lock_expires_at" db:"lock_expires_at"` + AdditionalDocumentsID *uuid.UUID `json:"additional_documents_id" db:"additional_documents_id"` + AdditionalDocuments *Document `belongs_to:"documents" fk_id:"additional_documents_id"` } // TableName overrides the table name used by Pop. diff --git a/pkg/services/move.go b/pkg/services/move.go index 1c42b9dfec0..ddee68ef1ba 100644 --- a/pkg/services/move.go +++ b/pkg/services/move.go @@ -9,6 +9,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/storage" ) // MoveListFetcher is the exported interface for fetching multiple moves @@ -71,6 +72,17 @@ type MoveExcessWeightUploader interface { ) (*models.Move, error) } +type MoveAdditionalDocumentsUploader interface { + CreateAdditionalDocumentsUpload( + appCtx appcontext.AppContext, + userID uuid.UUID, + moveID uuid.UUID, + file io.ReadCloser, + uploadFilename string, + storer storage.FileStorer, + ) (models.Upload, string, *validate.Errors, error) +} + // MoveFinancialReviewFlagSetter is the exported interface for flagging a move for financial review // //go:generate mockery --name MoveFinancialReviewFlagSetter diff --git a/pkg/services/move/additional_documents_uploader.go b/pkg/services/move/additional_documents_uploader.go new file mode 100644 index 00000000000..36a1670b2d0 --- /dev/null +++ b/pkg/services/move/additional_documents_uploader.go @@ -0,0 +1,226 @@ +package move + +import ( + "database/sql" + "io" + + "github.com/gobuffalo/validate/v3" + "github.com/gofrs/uuid" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/apperror" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/services" + "github.com/transcom/mymove/pkg/storage" + "github.com/transcom/mymove/pkg/uploader" +) + +type additionalDocumentsUploader struct { + uploadCreator services.UploadCreator + checks []validator +} + +// NewMoveExcessWeightUploader returns a new excessWeightUploader +func NewMoveAdditionalDocumentsUploader(uploadCreator services.UploadCreator) services.MoveAdditionalDocumentsUploader { + return &additionalDocumentsUploader{uploadCreator, basicChecks()} +} + +// // NewPrimeMoveExcessWeightUploader returns a new excessWeightUploader +// func NewPrimeMoveExcessWeightUploader(uploadCreator services.UploadCreator) services.MoveExcessWeightUploader { +// return &excessWeightUploader{uploadCreator, primeChecks()} +// } + +// CreateExcessWeightUpload uploads an excess weight document and updates the move with the new upload info +func (u *additionalDocumentsUploader) CreateAdditionalDocumentsUpload( + appCtx appcontext.AppContext, + userID uuid.UUID, + moveID uuid.UUID, + file io.ReadCloser, + filename string, + storer storage.FileStorer, + // ) (error) { +) (models.Upload, string, *validate.Errors, error) { + moveToUpdate, findErr := u.findMoveWithAdditionalDocuments(appCtx, moveID) + if findErr != nil { + return models.Upload{}, "", nil, findErr + } + + userUpload, url, verrs, err := u.additionalDoc(appCtx, userID, *moveToUpdate, file, filename, storer) + if verrs.HasAny() || err != nil { + return models.Upload{}, "", verrs, err + } + + return userUpload.Upload, url, nil, nil + + // return userUpload.Upload, url, nil, nil + // Get existing move + // move := &models.Move{} + // err := appCtx.DB().Find(move, moveID) + // if err != nil { + // switch err { + // case sql.ErrNoRows: + // return nil, apperror.NewNotFoundError(moveID, "while looking for move") + // default: + // return nil, apperror.NewQueryError("Move", err, "") + // } + // } + + // // Run the (read-only) validations + // if verr := validateMove(appCtx, *move, nil, u.checks...); verr != nil { + // return nil, verr + // } + + // // Open transaction to create upload and update the move + // txnErr := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { + // excessWeightUpload, err := u.uploadCreator.CreateUpload( + // txnAppCtx, file, fmt.Sprintf("move/%s/%s", move.ID, uploadFilename), uploadType) + // if err != nil { + // return err + // } + + // move.ExcessWeightUploadID = &excessWeightUpload.ID + // move.ExcessWeightUpload = excessWeightUpload + + // verrs, err := txnAppCtx.DB().ValidateAndUpdate(move) + // if verrs != nil && verrs.HasAny() { + // return apperror.NewInvalidInputError( + // move.ID, err, verrs, "Validation errors found while updating excess weight info on move") + // } else if err != nil { + // return apperror.NewQueryError("Move", err, "Failed to update excess weight info on move") + // } + + // return nil + // }) + // if txnErr != nil { + // return nil, txnErr + // } + + // return move, nil +} + +func (u *additionalDocumentsUploader) findMoveWithAdditionalDocuments(appCtx appcontext.AppContext, moveID uuid.UUID) (*models.Move, error) { + var move models.Move + + query := appCtx.DB().Q().Where("moves.id = ?", moveID) + + // if appCtx.Session().IsMilApp() { + // query = query.Where("moves.id = ?", moveID) + // } + + err := query.Find(&move, moveID) + + if err != nil { + switch err { + case sql.ErrNoRows: + return nil, apperror.NewNotFoundError(moveID, "while looking for order") + default: + return nil, apperror.NewQueryError("Order", err, "") + } + } + + return &move, nil +} + +func (u *additionalDocumentsUploader) additionalDoc(appCtx appcontext.AppContext, userID uuid.UUID, move models.Move, file io.ReadCloser, filename string, storer storage.FileStorer) (models.UserUpload, string, *validate.Errors, error) { + // DAD + // If Order does not have a Document for amended orders uploads, then create a new one + var err error + savedAdditionalDoc := move.AdditionalDocuments + if move.AdditionalDocuments == nil { + additionalDocument := &models.Document{ + ServiceMemberID: appCtx.Session().ServiceMemberID, + } + savedAdditionalDoc, err = u.saveAdditionalDocumentForMove(appCtx, additionalDocument) + if err != nil { + return models.UserUpload{}, "", nil, err + } + + // save new UploadedAmendedOrdersID (document ID) to orders + move.AdditionalDocuments = savedAdditionalDoc + move.AdditionalDocumentsID = &savedAdditionalDoc.ID + _, _, err = u.updateMove(appCtx, move) + if err != nil { + return models.UserUpload{}, "", nil, err + } + } + + // Create new user upload for amended order + var userUpload *models.UserUpload + var verrs *validate.Errors + var url string + userUpload, url, verrs, err = uploader.CreateUserUploadForDocumentWrapper( + appCtx, + userID, + storer, + file, + filename, + uploader.MaxCustomerUserUploadFileSizeLimit, + uploader.AllowedTypesServiceMember, + &savedAdditionalDoc.ID, + ) + + if verrs.HasAny() || err != nil { + return models.UserUpload{}, "", verrs, err + } + + move.AdditionalDocuments.UserUploads = append(move.AdditionalDocuments.UserUploads, *userUpload) + + return *userUpload, url, nil, nil +} + +func (u *additionalDocumentsUploader) saveAdditionalDocumentForMove(appCtx appcontext.AppContext, doc *models.Document) (*models.Document, error) { + // DAD + var docID uuid.UUID + if doc != nil { + docID = doc.ID + } + + transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { + var verrs *validate.Errors + var err error + verrs, err = txnAppCtx.DB().ValidateAndSave(doc) + return handleError(docID, verrs, err) + }) + + if transactionError != nil { + return nil, transactionError + } + + return doc, nil +} + +func (f *additionalDocumentsUploader) updateMove(appCtx appcontext.AppContext, move models.Move) (*models.Move, uuid.UUID, error) { + var returnedMove *models.Move + var err error + + transactionError := appCtx.NewTransaction(func(txnAppCtx appcontext.AppContext) error { + returnedMove, err = updateMoveInTx(txnAppCtx, move) + if err != nil { + return err + } + + return nil + }) + + if transactionError != nil { + return nil, uuid.Nil, transactionError + } + + // var moveID uuid.UUID + // if len(order.Moves) > 0 { + // moveID = order.Moves[0].ID + // } + return returnedMove, returnedMove.ID, nil +} + +func updateMoveInTx(appCtx appcontext.AppContext, move models.Move) (*models.Move, error) { + var verrs *validate.Errors + var err error + + verrs, err = appCtx.DB().ValidateAndUpdate(&move) + if e := handleError(move.ID, verrs, err); e != nil { + return nil, e + } + + return &move, nil +} diff --git a/pkg/services/order/order_updater.go b/pkg/services/order/order_updater.go index 9c7ed24badf..8e954ec1d13 100644 --- a/pkg/services/order/order_updater.go +++ b/pkg/services/order/order_updater.go @@ -100,6 +100,7 @@ func (f *orderUpdater) UpdateAllowanceAsCounselor(appCtx appcontext.AppContext, // UploadAmendedOrdersAsCustomer add amended order documents to an existing order func (f *orderUpdater) UploadAmendedOrdersAsCustomer(appCtx appcontext.AppContext, userID uuid.UUID, orderID uuid.UUID, file io.ReadCloser, filename string, storer storage.FileStorer) (models.Upload, string, *validate.Errors, error) { + // DAD orderToUpdate, findErr := f.findOrderWithAmendedOrders(appCtx, orderID) if findErr != nil { return models.Upload{}, "", nil, findErr @@ -245,7 +246,7 @@ func orderFromTOOPayload(_ appcontext.AppContext, existingOrder models.Order, pa } func (f *orderUpdater) amendedOrder(appCtx appcontext.AppContext, userID uuid.UUID, order models.Order, file io.ReadCloser, filename string, storer storage.FileStorer) (models.UserUpload, string, *validate.Errors, error) { - + // DAD // If Order does not have a Document for amended orders uploads, then create a new one var err error savedAmendedOrdersDoc := order.UploadedAmendedOrders @@ -490,7 +491,7 @@ func allowanceFromCounselingPayload(existingOrder models.Order, payload ghcmessa } func (f *orderUpdater) saveDocumentForAmendedOrder(appCtx appcontext.AppContext, doc *models.Document) (*models.Document, error) { - + // DAD var docID uuid.UUID if doc != nil { docID = doc.ID @@ -511,6 +512,8 @@ func (f *orderUpdater) saveDocumentForAmendedOrder(appCtx appcontext.AppContext, } func (f *orderUpdater) updateOrder(appCtx appcontext.AppContext, order models.Order, checks ...Validator) (*models.Order, uuid.UUID, error) { + //dad + var returnedOrder *models.Order var err error diff --git a/src/constants/routes.js b/src/constants/routes.js index f3afb3eec45..512062263d1 100644 --- a/src/constants/routes.js +++ b/src/constants/routes.js @@ -47,6 +47,8 @@ export const customerRoutes = { PROFILE_PATH: '/service-member/profile', SERVICE_INFO_EDIT_PATH: '/moves/review/edit-service-info', CONTACT_INFO_EDIT_PATH: '/moves/review/edit-contact-info', + UPLOAD_ADDITIONAL_DOCUMENTS_PATH: '/move/:moveLocator/upload-additional-documents', + UPLOAD_ADDITIONAL_DOCUMENTS_EDIT_PATH: '/move/:moveLocator/upload-additional-documents/:additionalDocumentsId', }; const BASE_COUNSELING_MOVE_PATH = '/counseling/moves/:moveCode'; diff --git a/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx b/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx new file mode 100644 index 00000000000..7114a32b367 --- /dev/null +++ b/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx @@ -0,0 +1,133 @@ +import { React, createRef, useEffect, useState } from 'react'; +import { GridContainer, Grid, Alert } from '@trussworks/react-uswds'; +import { useNavigate, useParams } from 'react-router-dom'; +import { connect } from 'react-redux'; +import { generatePath } from 'react-router'; + +import NotificationScrollToTop from 'components/NotificationScrollToTop'; +import SectionWrapper from 'components/Customer/SectionWrapper'; +import Hint from 'components/Hint'; +import UploadsTable from 'components/UploadsTable/UploadsTable'; +import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; +import FileUpload from 'components/FileUpload/FileUpload'; +import { createUploadForAdditionalDocuments, getMove } from 'services/internalApi'; +import { selectCurrentMove } from 'store/entities/selectors'; +import { customerRoutes } from 'constants/routes'; +import { updateMove } from 'store/entities/actions'; + +const AdditionalDocuments = ({ locator }) => { + const { moveId, additionalDocumentsId } = useParams(); + const filePondEl = createRef(); + const navigate = useNavigate(); + const [errorMessage, setErrorMessage] = useState(null); + + const uploads = []; + + // useEffect(() => { + // // if no existing documents create a new one + // if (!additionalDocumentsId) { + // createAdditionalDocuments(locator) + // .then((res) => { + // const path = generatePath(customerRoutes.UPLOAD_ADDITIONAL_DOCUMENTS_EDIT_PATH, { + // additionalDocumentsId: res.id, + // }); + // navigate(path, { replace: true }); + // }) + // .catch(() => {}); + // } + // }); + + const handleDelete = async (documentId) => { + // return deleteAdditionalDocuments(documentId).then(() => { + // getMove(moveId).then((res) => { + // updateMove(res); + // }); + // }); + }; + + const handleUpload = async (file) => { + return createUploadForAdditionalDocuments(file, '40b13561-349d-4e71-a051-eb2b9d69bdd4'); + }; + + // const handleUploadComplete = (err) => { + // if (err) { + // setErrorMessage('Encountered error when completing file upload'); + // } + // }; + + const onChange = () => { + // filePondEl.current?.removeFiles(); + // handleUploadComplete(); + }; + + const handleSave = () => { + navigate(-1); + }; + + const handleCancel = () => { + navigate(-1); + }; + + const warningMessage = + 'Documents uploaded here will not amend a customers move. Please upload new orders/amendments via the "Upload documents" link next to the Orders section of the customers move.'; + + return ( + + + + {errorMessage && ( + + + + {errorMessage} + + + + )} + + + +

Additional Documents

+

Upload any additional documentation that may help your services counselor complete your request.

+ + {warningMessage} + +
+
+ + + +
Upload documents
+ PDF, JPG, or PNG only. Maximum file size 25MB. Each page must be clear and legible + {/* {uploads?.length > 0 && ( */} + <> +
+ + + {/* )} */} +
+ choose from folder`} + labelIdleMobile={`Upload files`} + /> +
+ +
+
+
+
+ ); +}; + +const mapStateToProps = (state) => { + const { locator } = selectCurrentMove(state); + + const props = { locator }; + + return props; +}; + +export default connect(mapStateToProps)(AdditionalDocuments); diff --git a/src/pages/MyMove/Home/MoveHome.jsx b/src/pages/MyMove/Home/MoveHome.jsx index 1dca0e85e57..d7873ea0929 100644 --- a/src/pages/MyMove/Home/MoveHome.jsx +++ b/src/pages/MyMove/Home/MoveHome.jsx @@ -458,6 +458,26 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed {renderAlert()} {renderHelper()} + {hasSubmittedMove() && ( +
+ +
+ )} import('pages/MyMove/PPM/Closeout/Review/Review')); const ProGear = lazy(() => import('pages/MyMove/PPM/Closeout/ProGear/ProGear.jsx')); const Expenses = lazy(() => import('pages/MyMove/PPM/Closeout/Expenses/Expenses')); const PPMFinalCloseout = lazy(() => import('pages/MyMove/PPM/Closeout/FinalCloseout/FinalCloseout')); +const AdditionalDocuments = lazy(() => import('pages/MyMove/AdditionalDocuments/AdditionalDocuments')); export class CustomerApp extends Component { constructor(props) { @@ -264,6 +265,11 @@ export class CustomerApp extends Component { } /> } /> } /> + } + /> } /> } /> } /> @@ -357,6 +363,11 @@ export class CustomerApp extends Component { } /> } /> } /> + } + /> } /> } /> } /> diff --git a/src/services/internalApi.js b/src/services/internalApi.js index 78c1df3fb00..f199e02372a 100644 --- a/src/services/internalApi.js +++ b/src/services/internalApi.js @@ -220,6 +220,20 @@ export async function createUploadForAmendedOrdersDocument(file, ordersId) { ); } +export async function createUploadForAdditionalDocuments(file, moveId) { + debugger; + return makeInternalRequest( + 'moves.uploadAdditionalDocuments', + { + moveId, + file, + }, + { + normalize: false, + }, + ); +} + export async function createUploadForDocument(file, documentId) { return makeInternalRequest( 'uploads.createUpload', diff --git a/swagger-def/definitions/AdditionalDocuments.yaml b/swagger-def/definitions/AdditionalDocuments.yaml new file mode 100644 index 00000000000..6b886fdf75c --- /dev/null +++ b/swagger-def/definitions/AdditionalDocuments.yaml @@ -0,0 +1,42 @@ +description: Additional Documents for a move. +type: object +properties: + id: + description: Unique primary identifier of the Additional Documents object + type: string + format: uuid + example: c56a4180-65aa-42ec-a945-5fd21dec0538 + readOnly: true + moveId: + description: TheMove id that this Additional Document belongs to + type: string + format: uuid + example: c56a4180-65aa-42ec-a945-5fd21dec0538 + readOnly: true + documentId: + description: The id of the Document that contains all file uploads for this additional document + type: string + format: uuid + example: c56a4180-65aa-42ec-a945-5fd21dec0538 + readOnly: true + document: + allOf: + - description: The Document object that contains all file uploads for this additional document + - $ref: 'Document.yaml' + createdAt: + description: Timestamp the moving expense object was initially created in the system (UTC) + type: string + format: date-time + readOnly: true + updatedAt: + description: Timestamp when a property of this moving expense object was last modified (UTC) + type: string + format: date-time + readOnly: true +required: + - id + - createdAt + - updatedAt + - moveId + - documentId + - document \ No newline at end of file diff --git a/swagger-def/internal.yaml b/swagger-def/internal.yaml index 8e4afc1fdf1..ffa8996ac07 100644 --- a/swagger-def/internal.yaml +++ b/swagger-def/internal.yaml @@ -3655,6 +3655,82 @@ paths: $ref: "#/responses/UnprocessableEntity" "500": $ref: "#/responses/ServerError" + /moves/{moveId}/upload_additional_documents: + patch: + summary: Patch the amended orders for a given order + description: Patch the amended orders for a given order + operationId: uploadAdditionalDocuments + tags: + - moves + consumes: + - multipart/form-data + parameters: + - in: path + name: moveId + type: string + format: uuid + required: true + description: UUID of the order + - in: formData + name: file + type: file + description: The file to upload. + required: true + responses: + "201": + description: created upload + schema: + $ref: "definitions/Upload.yaml" + "400": + description: invalid request + schema: + $ref: "#/definitions/InvalidRequestResponsePayload" + "403": + description: not authorized + "404": + description: not found + "413": + description: payload is too large + "500": + description: server error + # /moves/{moveId}/additional_documents: + # patch: + # summary: asdf + # description: asdf + # operationID: uploadAdditionalDocuments + # tags: + # - moves + # consumes: + # - multipart/form-data + # parameters: + # - in: path + # name: moveId + # type: string + # format: uuid + # required: true + # description: UUID of the order + # - in: formData + # name: file + # type: file + # description: The file to upload. + # required: true + # responses: + # "201": + # description: created upload + # schema: + # $ref: "definitions/Upload.yaml" + # "400": + # description: invalid request + # schema: + # $ref: "#/definitions/InvalidRequestResponsePayload" + # "403": + # description: not authorized + # "404": + # description: not found + # "413": + # description: payload is too large + # "500": + # description: server error /ppm-shipments/{ppmShipmentId}/moving-expenses: post: summary: Creates moving expense document diff --git a/swagger/internal.yaml b/swagger/internal.yaml index 1ded0877216..189eff2fcc4 100644 --- a/swagger/internal.yaml +++ b/swagger/internal.yaml @@ -4957,6 +4957,44 @@ paths: $ref: '#/responses/UnprocessableEntity' '500': $ref: '#/responses/ServerError' + /moves/{moveId}/upload_additional_documents: + patch: + summary: Patch the amended orders for a given order + description: Patch the amended orders for a given order + operationId: uploadAdditionalDocuments + tags: + - moves + consumes: + - multipart/form-data + parameters: + - in: path + name: moveId + type: string + format: uuid + required: true + description: UUID of the order + - in: formData + name: file + type: file + description: The file to upload. + required: true + responses: + '201': + description: created upload + schema: + $ref: '#/definitions/Upload' + '400': + description: invalid request + schema: + $ref: '#/definitions/InvalidRequestResponsePayload' + '403': + description: not authorized + '404': + description: not found + '413': + description: payload is too large + '500': + description: server error /ppm-shipments/{ppmShipmentId}/moving-expenses: post: summary: Creates moving expense document From 758bdae5eeca950f2c1bca13328edb996e4f4a17 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Thu, 30 May 2024 19:16:12 +0000 Subject: [PATCH 02/10] work mostly complete, need testing and cleanup --- .envrc | 3 + migrations/app/migrations_manifest.txt | 1 + ...dditional_documents_id_col_to_moves.up.sql | 3 + pkg/gen/internalapi/embedded_spec.go | 20 +++++ .../uploads/delete_upload_parameters.go | 46 ++++++++++ .../uploads/delete_upload_urlbuilder.go | 9 ++ pkg/gen/internalmessages/move_payload.go | 51 +++++++++++ pkg/handlers/internalapi/moves.go | 26 +++--- pkg/handlers/internalapi/uploads.go | 24 +++++ pkg/models/move.go | 14 +++ .../move/additional_documents_uploader.go | 6 +- .../AdditionalDocuments.jsx | 87 +++++++++---------- .../AdditionalDocuments.test.jsx | 82 +++++++++++++++++ src/pages/MyMove/Home/MoveHome.jsx | 11 ++- src/services/internalApi.js | 14 ++- swagger-def/internal.yaml | 45 ++-------- swagger/internal.yaml | 7 ++ 17 files changed, 349 insertions(+), 100 deletions(-) create mode 100644 migrations/app/schema/20240529181303_add_additional_documents_id_col_to_moves.up.sql create mode 100644 src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.test.jsx diff --git a/.envrc b/.envrc index 8b3aca68844..58203d69c09 100644 --- a/.envrc +++ b/.envrc @@ -132,6 +132,9 @@ export FEATURE_FLAG_COUNSELOR_MOVE_CREATE=true export FEATURE_FLAG_MOVE_LOCK=false export FEATURE_FLAG_OKTA_DODID_INPUT=false +# Feature flag for additional supporting documents uploaded by customer +export FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=true + # Feature flags to disable certain shipment types export FEATURE_FLAG_PPM=true diff --git a/migrations/app/migrations_manifest.txt b/migrations/app/migrations_manifest.txt index b2c3b32dcc6..885a7673aa4 100644 --- a/migrations/app/migrations_manifest.txt +++ b/migrations/app/migrations_manifest.txt @@ -933,3 +933,4 @@ 20240513161626_updating_initial_value_for_validation_code.up.sql 20240515164336_ignore_locked_columns_in_moves_table_for_history_log.up.sql 20240516184021_import_pricing_data_ghc.up.sql +20240529181303_add_additional_documents_id_col_to_moves.up.sql diff --git a/migrations/app/schema/20240529181303_add_additional_documents_id_col_to_moves.up.sql b/migrations/app/schema/20240529181303_add_additional_documents_id_col_to_moves.up.sql new file mode 100644 index 00000000000..4234584c28c --- /dev/null +++ b/migrations/app/schema/20240529181303_add_additional_documents_id_col_to_moves.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE moves ADD COLUMN IF NOT EXISTS additional_documents_id uuid DEFAULT NULL; + +COMMENT ON COLUMN moves.additional_documents_id IS 'A foreign key that points to the document table for referencing additional documents'; \ No newline at end of file diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index 8bb8a64b102..8bf8923417f 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -3117,6 +3117,13 @@ func init() { "description": "ID of the order that the upload belongs to", "name": "orderId", "in": "query" + }, + { + "type": "string", + "format": "uuid", + "description": "ID of the move that the upload belongs to", + "name": "moveId", + "in": "query" } ], "responses": { @@ -4686,6 +4693,9 @@ func init() { "eTag" ], "properties": { + "additionalDocuments": { + "$ref": "#/definitions/Document" + }, "cancel_reason": { "type": "string", "x-nullable": true, @@ -10975,6 +10985,13 @@ func init() { "description": "ID of the order that the upload belongs to", "name": "orderId", "in": "query" + }, + { + "type": "string", + "format": "uuid", + "description": "ID of the move that the upload belongs to", + "name": "moveId", + "in": "query" } ], "responses": { @@ -12548,6 +12565,9 @@ func init() { "eTag" ], "properties": { + "additionalDocuments": { + "$ref": "#/definitions/Document" + }, "cancel_reason": { "type": "string", "x-nullable": true, diff --git a/pkg/gen/internalapi/internaloperations/uploads/delete_upload_parameters.go b/pkg/gen/internalapi/internaloperations/uploads/delete_upload_parameters.go index 91cad5f24f6..11151c8ace6 100644 --- a/pkg/gen/internalapi/internaloperations/uploads/delete_upload_parameters.go +++ b/pkg/gen/internalapi/internaloperations/uploads/delete_upload_parameters.go @@ -32,6 +32,10 @@ type DeleteUploadParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` + /*ID of the move that the upload belongs to + In: query + */ + MoveID *strfmt.UUID /*ID of the order that the upload belongs to In: query */ @@ -54,6 +58,11 @@ func (o *DeleteUploadParams) BindRequest(r *http.Request, route *middleware.Matc qs := runtime.Values(r.URL.Query()) + qMoveID, qhkMoveID, _ := qs.GetOK("moveId") + if err := o.bindMoveID(qMoveID, qhkMoveID, route.Formats); err != nil { + res = append(res, err) + } + qOrderID, qhkOrderID, _ := qs.GetOK("orderId") if err := o.bindOrderID(qOrderID, qhkOrderID, route.Formats); err != nil { res = append(res, err) @@ -69,6 +78,43 @@ func (o *DeleteUploadParams) BindRequest(r *http.Request, route *middleware.Matc return nil } +// bindMoveID binds and validates parameter MoveID from query. +func (o *DeleteUploadParams) bindMoveID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: false + // AllowEmptyValue: false + + if raw == "" { // empty values pass all other validations + return nil + } + + // Format: uuid + value, err := formats.Parse("uuid", raw) + if err != nil { + return errors.InvalidType("moveId", "query", "strfmt.UUID", raw) + } + o.MoveID = (value.(*strfmt.UUID)) + + if err := o.validateMoveID(formats); err != nil { + return err + } + + return nil +} + +// validateMoveID carries on validations for parameter MoveID +func (o *DeleteUploadParams) validateMoveID(formats strfmt.Registry) error { + + if err := validate.FormatOf("moveId", "query", "uuid", o.MoveID.String(), formats); err != nil { + return err + } + return nil +} + // bindOrderID binds and validates parameter OrderID from query. func (o *DeleteUploadParams) bindOrderID(rawData []string, hasKey bool, formats strfmt.Registry) error { var raw string diff --git a/pkg/gen/internalapi/internaloperations/uploads/delete_upload_urlbuilder.go b/pkg/gen/internalapi/internaloperations/uploads/delete_upload_urlbuilder.go index 6d0850522b1..24a03f0f7ef 100644 --- a/pkg/gen/internalapi/internaloperations/uploads/delete_upload_urlbuilder.go +++ b/pkg/gen/internalapi/internaloperations/uploads/delete_upload_urlbuilder.go @@ -18,6 +18,7 @@ import ( type DeleteUploadURL struct { UploadID strfmt.UUID + MoveID *strfmt.UUID OrderID *strfmt.UUID _basePath string @@ -61,6 +62,14 @@ func (o *DeleteUploadURL) Build() (*url.URL, error) { qs := make(url.Values) + var moveIDQ string + if o.MoveID != nil { + moveIDQ = o.MoveID.String() + } + if moveIDQ != "" { + qs.Set("moveId", moveIDQ) + } + var orderIDQ string if o.OrderID != nil { orderIDQ = o.OrderID.String() diff --git a/pkg/gen/internalmessages/move_payload.go b/pkg/gen/internalmessages/move_payload.go index 18a7958b0cb..a7d8d834419 100644 --- a/pkg/gen/internalmessages/move_payload.go +++ b/pkg/gen/internalmessages/move_payload.go @@ -19,6 +19,9 @@ import ( // swagger:model MovePayload type MovePayload struct { + // additional documents + AdditionalDocuments *Document `json:"additionalDocuments,omitempty"` + // cancel reason // Example: Change of orders CancelReason *string `json:"cancel_reason,omitempty"` @@ -83,6 +86,10 @@ type MovePayload struct { func (m *MovePayload) Validate(formats strfmt.Registry) error { var res []error + if err := m.validateAdditionalDocuments(formats); err != nil { + res = append(res, err) + } + if err := m.validateCloseoutOffice(formats); err != nil { res = append(res, err) } @@ -137,6 +144,25 @@ func (m *MovePayload) Validate(formats strfmt.Registry) error { return nil } +func (m *MovePayload) validateAdditionalDocuments(formats strfmt.Registry) error { + if swag.IsZero(m.AdditionalDocuments) { // not required + return nil + } + + if m.AdditionalDocuments != nil { + if err := m.AdditionalDocuments.Validate(formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("additionalDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("additionalDocuments") + } + return err + } + } + + return nil +} + func (m *MovePayload) validateCloseoutOffice(formats strfmt.Registry) error { if swag.IsZero(m.CloseoutOffice) { // not required return nil @@ -300,6 +326,10 @@ func (m *MovePayload) validateUpdatedAt(formats strfmt.Registry) error { func (m *MovePayload) ContextValidate(ctx context.Context, formats strfmt.Registry) error { var res []error + if err := m.contextValidateAdditionalDocuments(ctx, formats); err != nil { + res = append(res, err) + } + if err := m.contextValidateCloseoutOffice(ctx, formats); err != nil { res = append(res, err) } @@ -326,6 +356,27 @@ func (m *MovePayload) ContextValidate(ctx context.Context, formats strfmt.Regist return nil } +func (m *MovePayload) contextValidateAdditionalDocuments(ctx context.Context, formats strfmt.Registry) error { + + if m.AdditionalDocuments != nil { + + if swag.IsZero(m.AdditionalDocuments) { // not required + return nil + } + + if err := m.AdditionalDocuments.ContextValidate(ctx, formats); err != nil { + if ve, ok := err.(*errors.Validation); ok { + return ve.ValidateName("additionalDocuments") + } else if ce, ok := err.(*errors.CompositeError); ok { + return ce.ValidateName("additionalDocuments") + } + return err + } + } + + return nil +} + func (m *MovePayload) contextValidateCloseoutOffice(ctx context.Context, formats strfmt.Registry) error { if m.CloseoutOffice != nil { diff --git a/pkg/handlers/internalapi/moves.go b/pkg/handlers/internalapi/moves.go index 1ea59699139..6486ba19267 100644 --- a/pkg/handlers/internalapi/moves.go +++ b/pkg/handlers/internalapi/moves.go @@ -41,17 +41,23 @@ func payloadForMoveModel(storer storage.FileStorer, order models.Order, move mod eTag := etag.GenerateEtag(move.UpdatedAt) + var additionalDocumentsPayload *internalmessages.Document + if move.AdditionalDocuments != nil { + additionalDocumentsPayload, _ = payloads.PayloadForDocumentModel(storer, *move.AdditionalDocuments) + } + movePayload := &internalmessages.MovePayload{ - CreatedAt: handlers.FmtDateTime(move.CreatedAt), - SubmittedAt: handlers.FmtDateTime(SubmittedAt), - Locator: models.StringPointer(move.Locator), - ID: handlers.FmtUUID(move.ID), - UpdatedAt: handlers.FmtDateTime(move.UpdatedAt), - MtoShipments: mtoPayloads, - OrdersID: handlers.FmtUUID(order.ID), - ServiceMemberID: *handlers.FmtUUID(order.ServiceMemberID), - Status: internalmessages.MoveStatus(move.Status), - ETag: &eTag, + CreatedAt: handlers.FmtDateTime(move.CreatedAt), + SubmittedAt: handlers.FmtDateTime(SubmittedAt), + Locator: models.StringPointer(move.Locator), + ID: handlers.FmtUUID(move.ID), + UpdatedAt: handlers.FmtDateTime(move.UpdatedAt), + MtoShipments: mtoPayloads, + OrdersID: handlers.FmtUUID(order.ID), + ServiceMemberID: *handlers.FmtUUID(order.ServiceMemberID), + Status: internalmessages.MoveStatus(move.Status), + ETag: &eTag, + AdditionalDocuments: additionalDocumentsPayload, } if move.CloseoutOffice != nil { diff --git a/pkg/handlers/internalapi/uploads.go b/pkg/handlers/internalapi/uploads.go index e9e67d155a3..b283cff1599 100644 --- a/pkg/handlers/internalapi/uploads.go +++ b/pkg/handlers/internalapi/uploads.go @@ -151,6 +151,30 @@ func (h DeleteUploadHandler) Handle(params uploadop.DeleteUploadParams) middlewa return uploadop.NewDeleteUploadNoContent(), nil } + + if params.MoveID != nil { + moveID, _ := uuid.FromString(params.MoveID.String()) + move, e := models.FetchMoveByMoveID(appCtx.DB(), moveID) + fmt.Println(move) + + if e != nil { + return handlers.ResponseForError(appCtx.Logger(), e), e + } + + userUploader, e := uploaderpkg.NewUserUploader( + h.FileStorer(), + uploaderpkg.MaxCustomerUserUploadFileSizeLimit, + ) + if e != nil { + appCtx.Logger().Fatal("could not instantiate uploader", zap.Error(e)) + } + if e = userUploader.DeleteUserUpload(appCtx, &userUpload); e != nil { + return handlers.ResponseForError(appCtx.Logger(), e), e + } + + return uploadop.NewDeleteUploadNoContent(), nil + } + //Fetch upload information so we can retrieve the move status uploadInformation, err := h.FetchUploadInformation(appCtx, uploadID) if err != nil { diff --git a/pkg/models/move.go b/pkg/models/move.go index 52e56bd234c..1d79a82959e 100644 --- a/pkg/models/move.go +++ b/pkg/models/move.go @@ -128,6 +128,8 @@ func FetchMove(db *pop.Connection, session *auth.Session, id uuid.UUID) (*Move, "Orders.UploadedAmendedOrders", "CloseoutOffice", "LockedByOfficeUser", + "AdditionalDocuments", + "AdditionalDocuments.UserUploads", ).Where("show = TRUE").Find(&move, id) if err != nil { @@ -160,6 +162,18 @@ func FetchMove(db *pop.Connection, session *auth.Session, id uuid.UUID) (*Move, } move.MTOShipments = shipments + if move.AdditionalDocumentsID != nil { + var additionalDocumentUploads UserUploads + err = db.Q(). + Scope(utilities.ExcludeDeletedScope()).EagerPreload("Upload"). + Where("document_id = ?", move.AdditionalDocumentsID). + All(&additionalDocumentUploads) + if err != nil { + return &move, err + } + move.AdditionalDocuments.UserUploads = additionalDocumentUploads + } + // Ensure that the logged-in user is authorized to access this move if session.IsMilApp() && move.Orders.ServiceMember.ID != session.ServiceMemberID { return nil, ErrFetchForbidden diff --git a/pkg/services/move/additional_documents_uploader.go b/pkg/services/move/additional_documents_uploader.go index 36a1670b2d0..955ac688197 100644 --- a/pkg/services/move/additional_documents_uploader.go +++ b/pkg/services/move/additional_documents_uploader.go @@ -101,11 +101,7 @@ func (u *additionalDocumentsUploader) CreateAdditionalDocumentsUpload( func (u *additionalDocumentsUploader) findMoveWithAdditionalDocuments(appCtx appcontext.AppContext, moveID uuid.UUID) (*models.Move, error) { var move models.Move - query := appCtx.DB().Q().Where("moves.id = ?", moveID) - - // if appCtx.Session().IsMilApp() { - // query = query.Where("moves.id = ?", moveID) - // } + query := appCtx.DB().Q().EagerPreload("AdditionalDocuments").Where("moves.id = ?", moveID) err := query.Find(&move, moveID) diff --git a/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx b/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx index 7114a32b367..e261eb23e8b 100644 --- a/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx +++ b/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx @@ -1,5 +1,5 @@ import { React, createRef, useEffect, useState } from 'react'; -import { GridContainer, Grid, Alert } from '@trussworks/react-uswds'; +import { GridContainer, Grid, Alert, Button } from '@trussworks/react-uswds'; import { useNavigate, useParams } from 'react-router-dom'; import { connect } from 'react-redux'; import { generatePath } from 'react-router'; @@ -10,63 +10,55 @@ import Hint from 'components/Hint'; import UploadsTable from 'components/UploadsTable/UploadsTable'; import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; import FileUpload from 'components/FileUpload/FileUpload'; -import { createUploadForAdditionalDocuments, getMove } from 'services/internalApi'; +import { createUploadForAdditionalDocuments, deleteAdditionalDocumentUpload, getMove } from 'services/internalApi'; import { selectCurrentMove } from 'store/entities/selectors'; import { customerRoutes } from 'constants/routes'; -import { updateMove } from 'store/entities/actions'; +import { updateMove as updateMoveAction } from 'store/entities/actions'; +import LoadingPlaceholder from 'shared/LoadingPlaceholder'; -const AdditionalDocuments = ({ locator }) => { - const { moveId, additionalDocumentsId } = useParams(); +const AdditionalDocuments = ({ move, updateMove }) => { + const [isLoading, setIsLoading] = useState(true); + const moveId = move?.id; const filePondEl = createRef(); const navigate = useNavigate(); const [errorMessage, setErrorMessage] = useState(null); - - const uploads = []; - - // useEffect(() => { - // // if no existing documents create a new one - // if (!additionalDocumentsId) { - // createAdditionalDocuments(locator) - // .then((res) => { - // const path = generatePath(customerRoutes.UPLOAD_ADDITIONAL_DOCUMENTS_EDIT_PATH, { - // additionalDocumentsId: res.id, - // }); - // navigate(path, { replace: true }); - // }) - // .catch(() => {}); - // } - // }); - - const handleDelete = async (documentId) => { - // return deleteAdditionalDocuments(documentId).then(() => { - // getMove(moveId).then((res) => { - // updateMove(res); - // }); - // }); + const uploads = move?.additionalDocuments?.uploads; + + const handleDelete = async (uploadId) => { + return deleteAdditionalDocumentUpload(uploadId, moveId).then(() => { + getMove(moveId).then((res) => { + updateMove(res); + }); + }); }; const handleUpload = async (file) => { - return createUploadForAdditionalDocuments(file, '40b13561-349d-4e71-a051-eb2b9d69bdd4'); + return createUploadForAdditionalDocuments(file, moveId); }; - // const handleUploadComplete = (err) => { - // if (err) { - // setErrorMessage('Encountered error when completing file upload'); - // } - // }; + const handleUploadComplete = () => { + getMove(moveId).then((res) => { + updateMove(res); + }); + }; const onChange = () => { - // filePondEl.current?.removeFiles(); - // handleUploadComplete(); + filePondEl.current?.removeFiles(); + handleUploadComplete(); }; - const handleSave = () => { + const handleBack = () => { navigate(-1); }; - const handleCancel = () => { - navigate(-1); - }; + useEffect(() => { + getMove(moveId).then((res) => { + updateMove(res); + setIsLoading(false); + }); + }, [updateMove, moveId]); + + if (isLoading) return ; const warningMessage = 'Documents uploaded here will not amend a customers move. Please upload new orders/amendments via the "Upload documents" link next to the Orders section of the customers move.'; @@ -102,7 +94,7 @@ const AdditionalDocuments = ({ locator }) => { {/* {uploads?.length > 0 && ( */} <>
- + {/* )} */}
@@ -114,7 +106,8 @@ const AdditionalDocuments = ({ locator }) => { labelIdleMobile={`Upload files`} />
- + + {/* */}
@@ -123,11 +116,15 @@ const AdditionalDocuments = ({ locator }) => { }; const mapStateToProps = (state) => { - const { locator } = selectCurrentMove(state); + const move = selectCurrentMove(state); - const props = { locator }; + const props = { move }; return props; }; -export default connect(mapStateToProps)(AdditionalDocuments); +const mDTP = { + updateMove: updateMoveAction, +}; + +export default connect(mapStateToProps, mDTP)(AdditionalDocuments); diff --git a/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.test.jsx b/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.test.jsx new file mode 100644 index 00000000000..51461ecdfcc --- /dev/null +++ b/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.test.jsx @@ -0,0 +1,82 @@ +import { React } from 'react'; +import { screen, waitFor } from '@testing-library/react'; + +import AdditionalDocuments from './AdditionalDocuments'; + +import { renderWithProviders } from 'testUtils'; +import { getMove } from 'services/internalApi'; +import { selectCurrentMove } from 'store/entities/selectors'; + +jest.mock('store/entities/selectors', () => ({ + ...jest.requireActual('store/entities/selectors'), + selectCurrentMove: jest.fn(), +})); + +jest.mock('services/internalApi', () => ({ + ...jest.requireActual('services/internalApi'), + getMove: jest.fn().mockImplementation(() => Promise.resolve()), +})); + +const testMove = { + additionalDocuments: { + id: 'c43ae36e-4e15-4cb3-865a-e4dccffa0df7', + service_member_id: 'dfdd3e21-3988-4104-a5c2-06b195f9b7f0', + uploads: [ + { + bytes: 120653, + contentType: 'application/pdf', + createdAt: '2024-05-29T19:14:39.108Z', + filename: '9380-Statement-20240430.pdf', + id: 'c3c0cda9-a77e-4b8b-8b8b-67ccadc3c862', + status: 'PROCESSING', + updatedAt: '2024-05-29T19:14:39.108Z', + url: '/storage/user/accf760b-2e3d-4af8-a59b-c10b591dcc15/uploads/c3c0cda9-a77e-4b8b-8b8b-67ccadc3c862?contentType=application%2Fpdf', + }, + { + bytes: 307051, + contentType: 'image/png', + createdAt: '2024-05-30T04:23:27.241Z', + filename: 'Screenshot 2024-05-16 at 3.33.52 PM.png', + id: '70a35ab0-a3f5-44a3-8702-0bb7d0c568c8', + status: 'PROCESSING', + updatedAt: '2024-05-30T04:23:27.241Z', + url: '/storage/user/accf760b-2e3d-4af8-a59b-c10b591dcc15/uploads/70a35ab0-a3f5-44a3-8702-0bb7d0c568c8?contentType=image%2Fpng', + }, + { + bytes: 82301, + contentType: 'image/png', + createdAt: '2024-05-30T04:33:10.622Z', + filename: 'Screenshot 2024-05-17 at 1.09.21 PM.png', + id: 'b11c0130-2403-4287-b464-4c5ac17797b3', + status: 'PROCESSING', + updatedAt: '2024-05-30T04:33:10.622Z', + url: '/storage/user/accf760b-2e3d-4af8-a59b-c10b591dcc15/uploads/b11c0130-2403-4287-b464-4c5ac17797b3?contentType=image%2Fpng', + }, + ], + }, + created_at: '2024-05-29T18:46:17.808Z', + eTag: 'MjAyNC0wNS0yOVQxOToxNDozOS4xMDQyNzJa', + id: '43a369e8-5fa3-4a13-9d9a-36d86731c1da', + locator: '988HDJ', + mto_shipments: ['c93bf4d1-1470-4c50-b2b6-f736abd2986a'], + orders_id: '69967de3-3d9d-4e73-a497-f401884393bf', + primeCounselingCompletedAt: '0001-01-01T00:00:00.000Z', + service_member_id: 'dfdd3e21-3988-4104-a5c2-06b195f9b7f0', + status: 'NEEDS SERVICE COUNSELING', + submitted_at: '2024-05-29T18:47:26.360Z', + updated_at: '2024-05-29T19:14:39.104Z', +}; + +describe('Additional Documents component', () => { + const testProps = { + move: testMove, + updateMove: jest.fn(), + }; + it('renders all content of AdditionalDocuments', () => { + selectCurrentMove.mockImplementation(() => testMove); + getMove.mockResolvedValue(testMove); + renderWithProviders(); + + expect(screen.getByLabelText('Upload')).toBeInTheDocument(); + }); +}); diff --git a/src/pages/MyMove/Home/MoveHome.jsx b/src/pages/MyMove/Home/MoveHome.jsx index d7873ea0929..208106f123d 100644 --- a/src/pages/MyMove/Home/MoveHome.jsx +++ b/src/pages/MyMove/Home/MoveHome.jsx @@ -54,6 +54,7 @@ import { formatCustomerDate, formatWeight } from 'utils/formatters'; import { isPPMAboutInfoComplete, isPPMShipmentComplete, isWeightTicketComplete } from 'utils/shipments'; import withRouter from 'utils/routing'; import { ADVANCE_STATUSES } from 'constants/ppms'; +import { isBooleanFlagEnabled } from 'utils/featureFlags'; const Description = ({ className, children, dataTestId }) => (

@@ -83,6 +84,14 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed const [showDeleteSuccessAlert, setShowDeleteSuccessAlert] = useState(false); const [showDeleteErrorAlert, setShowDeleteErrorAlert] = useState(false); const [showErrorAlert, setShowErrorAlert] = useState(false); + const [isManageSupportingDocsEnabled, setIsManageSupportingDocsEnabled] = useState(false); + + useEffect(() => { + const fetchData = async () => { + setIsManageSupportingDocsEnabled(await isBooleanFlagEnabled('manage_supporting_docs')); + }; + fetchData(); + }, []); // fetching all move data on load since this component is dependent on that data // this will run each time the component is loaded/accessed @@ -458,7 +467,7 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed {renderAlert()} {renderHelper()} - {hasSubmittedMove() && ( + {isManageSupportingDocsEnabled && hasSubmittedMove() && (

-
- )} handleNewPathClick(profileEditPath)} + actionBtnLabel={isAdditionalDocumentsButtonAvailable() ? 'Upload Additional Documents' : null} + onActionBtnClick={() => additionalDocumentsClick()} > Make sure to keep your personal information up to date during your move. From 8684d96f600a5a146a114de6580fdf3952f0111b Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Wed, 5 Jun 2024 03:47:19 +0000 Subject: [PATCH 07/10] code clean up --- pkg/gen/internalapi/embedded_spec.go | 16 +++++++------- .../moves/upload_additional_documents.go | 6 ++--- .../upload_additional_documents_urlbuilder.go | 2 +- .../internaloperations/mymove_api.go | 2 +- .../uploads/delete_upload_parameters.go | 2 +- pkg/handlers/internalapi/moves.go | 6 ++++- pkg/handlers/internalapi/uploads.go | 8 ++++--- .../move/additional_documents_uploader.go | 14 ++++++------ .../additional_documents_uploader_test.go | 2 +- pkg/services/order/order_updater.go | 5 ----- src/constants/routes.js | 1 - .../AdditionalDocuments.jsx | 22 +++---------------- swagger-def/internal.yaml | 8 +++---- swagger/internal.yaml | 11 ++++++---- 14 files changed, 46 insertions(+), 59 deletions(-) diff --git a/pkg/gen/internalapi/embedded_spec.go b/pkg/gen/internalapi/embedded_spec.go index 8806d6cff85..7d1cd0fb3fa 100644 --- a/pkg/gen/internalapi/embedded_spec.go +++ b/pkg/gen/internalapi/embedded_spec.go @@ -1183,16 +1183,16 @@ func init() { } } }, - "/moves/{moveId}/upload_additional_documents": { + "/moves/{moveId}/uploadAdditionalDocuments": { "patch": { - "description": "Patch the amended orders for a given order", + "description": "Customers will on occaision need the ability to upload additional supporting documents, for a variety of reasons. This does not include amended order.", "consumes": [ "multipart/form-data" ], "tags": [ "moves" ], - "summary": "Patch the amended orders for a given order", + "summary": "Patch the additional documents for a given move", "operationId": "uploadAdditionalDocuments", "parameters": [ { @@ -3121,7 +3121,7 @@ func init() { { "type": "string", "format": "uuid", - "description": "ID of the move that the upload belongs to", + "description": "Optional ID of the move that the upload belongs to", "name": "moveId", "in": "query" } @@ -8677,16 +8677,16 @@ func init() { } } }, - "/moves/{moveId}/upload_additional_documents": { + "/moves/{moveId}/uploadAdditionalDocuments": { "patch": { - "description": "Patch the amended orders for a given order", + "description": "Customers will on occaision need the ability to upload additional supporting documents, for a variety of reasons. This does not include amended order.", "consumes": [ "multipart/form-data" ], "tags": [ "moves" ], - "summary": "Patch the amended orders for a given order", + "summary": "Patch the additional documents for a given move", "operationId": "uploadAdditionalDocuments", "parameters": [ { @@ -11043,7 +11043,7 @@ func init() { { "type": "string", "format": "uuid", - "description": "ID of the move that the upload belongs to", + "description": "Optional ID of the move that the upload belongs to", "name": "moveId", "in": "query" } diff --git a/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents.go b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents.go index 0504b54a57a..ca7373bd49c 100644 --- a/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents.go +++ b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents.go @@ -30,11 +30,11 @@ func NewUploadAdditionalDocuments(ctx *middleware.Context, handler UploadAdditio } /* - UploadAdditionalDocuments swagger:route PATCH /moves/{moveId}/upload_additional_documents moves uploadAdditionalDocuments + UploadAdditionalDocuments swagger:route PATCH /moves/{moveId}/uploadAdditionalDocuments moves uploadAdditionalDocuments -# Patch the amended orders for a given order +# Patch the additional documents for a given move -Patch the amended orders for a given order +Customers will on occaision need the ability to upload additional supporting documents, for a variety of reasons. This does not include amended order. */ type UploadAdditionalDocuments struct { Context *middleware.Context diff --git a/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_urlbuilder.go b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_urlbuilder.go index bef9f4491c8..2eebbbffd3d 100644 --- a/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_urlbuilder.go +++ b/pkg/gen/internalapi/internaloperations/moves/upload_additional_documents_urlbuilder.go @@ -42,7 +42,7 @@ func (o *UploadAdditionalDocumentsURL) SetBasePath(bp string) { func (o *UploadAdditionalDocumentsURL) Build() (*url.URL, error) { var _result url.URL - var _path = "/moves/{moveId}/upload_additional_documents" + var _path = "/moves/{moveId}/uploadAdditionalDocuments" moveID := o.MoveID.String() if moveID != "" { diff --git a/pkg/gen/internalapi/internaloperations/mymove_api.go b/pkg/gen/internalapi/internaloperations/mymove_api.go index 5d4e294fac7..dcee8b1e2ca 100644 --- a/pkg/gen/internalapi/internaloperations/mymove_api.go +++ b/pkg/gen/internalapi/internaloperations/mymove_api.go @@ -1076,7 +1076,7 @@ func (o *MymoveAPI) initHandlerCache() { if o.handlers["PATCH"] == nil { o.handlers["PATCH"] = make(map[string]http.Handler) } - o.handlers["PATCH"]["/moves/{moveId}/upload_additional_documents"] = moves.NewUploadAdditionalDocuments(o.context, o.MovesUploadAdditionalDocumentsHandler) + o.handlers["PATCH"]["/moves/{moveId}/uploadAdditionalDocuments"] = moves.NewUploadAdditionalDocuments(o.context, o.MovesUploadAdditionalDocumentsHandler) if o.handlers["PATCH"] == nil { o.handlers["PATCH"] = make(map[string]http.Handler) } diff --git a/pkg/gen/internalapi/internaloperations/uploads/delete_upload_parameters.go b/pkg/gen/internalapi/internaloperations/uploads/delete_upload_parameters.go index 11151c8ace6..11e94d307a6 100644 --- a/pkg/gen/internalapi/internaloperations/uploads/delete_upload_parameters.go +++ b/pkg/gen/internalapi/internaloperations/uploads/delete_upload_parameters.go @@ -32,7 +32,7 @@ type DeleteUploadParams struct { // HTTP Request Object HTTPRequest *http.Request `json:"-"` - /*ID of the move that the upload belongs to + /*Optional ID of the move that the upload belongs to In: query */ MoveID *strfmt.UUID diff --git a/pkg/handlers/internalapi/moves.go b/pkg/handlers/internalapi/moves.go index 6486ba19267..0c8f483f19e 100644 --- a/pkg/handlers/internalapi/moves.go +++ b/pkg/handlers/internalapi/moves.go @@ -42,8 +42,12 @@ func payloadForMoveModel(storer storage.FileStorer, order models.Order, move mod eTag := etag.GenerateEtag(move.UpdatedAt) var additionalDocumentsPayload *internalmessages.Document + var err error if move.AdditionalDocuments != nil { - additionalDocumentsPayload, _ = payloads.PayloadForDocumentModel(storer, *move.AdditionalDocuments) + additionalDocumentsPayload, err = payloads.PayloadForDocumentModel(storer, *move.AdditionalDocuments) + } + if err != nil { + return nil, err } movePayload := &internalmessages.MovePayload{ diff --git a/pkg/handlers/internalapi/uploads.go b/pkg/handlers/internalapi/uploads.go index b283cff1599..e57c0673618 100644 --- a/pkg/handlers/internalapi/uploads.go +++ b/pkg/handlers/internalapi/uploads.go @@ -153,9 +153,11 @@ func (h DeleteUploadHandler) Handle(params uploadop.DeleteUploadParams) middlewa } if params.MoveID != nil { - moveID, _ := uuid.FromString(params.MoveID.String()) - move, e := models.FetchMoveByMoveID(appCtx.DB(), moveID) - fmt.Println(move) + moveID, e := uuid.FromString(params.MoveID.String()) + if e != nil { + appCtx.Logger().Error(fmt.Sprintf("UUID Parsing for %s", moveID.String()), zap.Error(err)) + return handlers.ResponseForError(appCtx.Logger(), e), e + } if e != nil { return handlers.ResponseForError(appCtx.Logger(), e), e diff --git a/pkg/services/move/additional_documents_uploader.go b/pkg/services/move/additional_documents_uploader.go index 724aec8d97f..e5a0a148b9d 100644 --- a/pkg/services/move/additional_documents_uploader.go +++ b/pkg/services/move/additional_documents_uploader.go @@ -20,12 +20,12 @@ type additionalDocumentsUploader struct { checks []validator } -// NewMoveExcessWeightUploader returns a new excessWeightUploader +// NewMoveAdditionalDocumentsUploader returns a new additionalDocumentsUploader func NewMoveAdditionalDocumentsUploader(uploadCreator services.UploadCreator) services.MoveAdditionalDocumentsUploader { return &additionalDocumentsUploader{uploadCreator, basicChecks()} } -// CreateExcessWeightUpload uploads an excess weight document and updates the move with the new upload info +// CreateAdditionalDocumentsUpload uploads an additional document and updates the move with the new upload info func (u *additionalDocumentsUploader) CreateAdditionalDocumentsUpload( appCtx appcontext.AppContext, userID uuid.UUID, @@ -57,9 +57,9 @@ func (u *additionalDocumentsUploader) findMoveWithAdditionalDocuments(appCtx app if err != nil { switch err { case sql.ErrNoRows: - return nil, apperror.NewNotFoundError(moveID, "while looking for order") + return nil, apperror.NewNotFoundError(moveID, "while looking for move") default: - return nil, apperror.NewQueryError("Order", err, "") + return nil, apperror.NewQueryError("Move", err, "") } } @@ -67,7 +67,7 @@ func (u *additionalDocumentsUploader) findMoveWithAdditionalDocuments(appCtx app } func (u *additionalDocumentsUploader) additionalDoc(appCtx appcontext.AppContext, userID uuid.UUID, move models.Move, file io.ReadCloser, filename string, storer storage.FileStorer) (models.UserUpload, string, *validate.Errors, error) { - // If Order does not have a Document for amended orders uploads, then create a new one + // If move does not have a Document for additional document uploads, then create a new one var err error savedAdditionalDoc := move.AdditionalDocuments if move.AdditionalDocuments == nil { @@ -79,7 +79,7 @@ func (u *additionalDocumentsUploader) additionalDoc(appCtx appcontext.AppContext return models.UserUpload{}, "", nil, err } - // save new UploadedAmendedOrdersID (document ID) to orders + // save new AdditionalDocumentID (document ID) to move move.AdditionalDocuments = savedAdditionalDoc move.AdditionalDocumentsID = &savedAdditionalDoc.ID _, _, err = u.updateMove(appCtx, move) @@ -88,7 +88,7 @@ func (u *additionalDocumentsUploader) additionalDoc(appCtx appcontext.AppContext } } - // Create new user upload for amended order + // Create new user upload for additional document var userUpload *models.UserUpload var verrs *validate.Errors var url string diff --git a/pkg/services/move/additional_documents_uploader_test.go b/pkg/services/move/additional_documents_uploader_test.go index 2a1516c3b10..5bba2de71e3 100644 --- a/pkg/services/move/additional_documents_uploader_test.go +++ b/pkg/services/move/additional_documents_uploader_test.go @@ -99,7 +99,7 @@ func (suite *MoveServiceSuite) TestAdditionalDocumentUploader() { suite.NotEmpty(url, "URL is populated") }) - suite.Run("Saves userUpload payload to order.UploadedAmendedOrders if the document already exists", func() { + suite.Run("Saves userUpload if the document already exists", func() { order := setUpOrders(true) appCtx := suite.AppContextWithSessionForTest(&auth.Session{ diff --git a/pkg/services/order/order_updater.go b/pkg/services/order/order_updater.go index 8e954ec1d13..b94d868d252 100644 --- a/pkg/services/order/order_updater.go +++ b/pkg/services/order/order_updater.go @@ -100,7 +100,6 @@ func (f *orderUpdater) UpdateAllowanceAsCounselor(appCtx appcontext.AppContext, // UploadAmendedOrdersAsCustomer add amended order documents to an existing order func (f *orderUpdater) UploadAmendedOrdersAsCustomer(appCtx appcontext.AppContext, userID uuid.UUID, orderID uuid.UUID, file io.ReadCloser, filename string, storer storage.FileStorer) (models.Upload, string, *validate.Errors, error) { - // DAD orderToUpdate, findErr := f.findOrderWithAmendedOrders(appCtx, orderID) if findErr != nil { return models.Upload{}, "", nil, findErr @@ -246,7 +245,6 @@ func orderFromTOOPayload(_ appcontext.AppContext, existingOrder models.Order, pa } func (f *orderUpdater) amendedOrder(appCtx appcontext.AppContext, userID uuid.UUID, order models.Order, file io.ReadCloser, filename string, storer storage.FileStorer) (models.UserUpload, string, *validate.Errors, error) { - // DAD // If Order does not have a Document for amended orders uploads, then create a new one var err error savedAmendedOrdersDoc := order.UploadedAmendedOrders @@ -491,7 +489,6 @@ func allowanceFromCounselingPayload(existingOrder models.Order, payload ghcmessa } func (f *orderUpdater) saveDocumentForAmendedOrder(appCtx appcontext.AppContext, doc *models.Document) (*models.Document, error) { - // DAD var docID uuid.UUID if doc != nil { docID = doc.ID @@ -512,8 +509,6 @@ func (f *orderUpdater) saveDocumentForAmendedOrder(appCtx appcontext.AppContext, } func (f *orderUpdater) updateOrder(appCtx appcontext.AppContext, order models.Order, checks ...Validator) (*models.Order, uuid.UUID, error) { - //dad - var returnedOrder *models.Order var err error diff --git a/src/constants/routes.js b/src/constants/routes.js index 512062263d1..9655ccfeb15 100644 --- a/src/constants/routes.js +++ b/src/constants/routes.js @@ -48,7 +48,6 @@ export const customerRoutes = { SERVICE_INFO_EDIT_PATH: '/moves/review/edit-service-info', CONTACT_INFO_EDIT_PATH: '/moves/review/edit-contact-info', UPLOAD_ADDITIONAL_DOCUMENTS_PATH: '/move/:moveLocator/upload-additional-documents', - UPLOAD_ADDITIONAL_DOCUMENTS_EDIT_PATH: '/move/:moveLocator/upload-additional-documents/:additionalDocumentsId', }; const BASE_COUNSELING_MOVE_PATH = '/counseling/moves/:moveCode'; diff --git a/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx b/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx index e261eb23e8b..53bce5648ee 100644 --- a/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx +++ b/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx @@ -1,27 +1,23 @@ import { React, createRef, useEffect, useState } from 'react'; import { GridContainer, Grid, Alert, Button } from '@trussworks/react-uswds'; -import { useNavigate, useParams } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import { connect } from 'react-redux'; -import { generatePath } from 'react-router'; -import NotificationScrollToTop from 'components/NotificationScrollToTop'; import SectionWrapper from 'components/Customer/SectionWrapper'; import Hint from 'components/Hint'; import UploadsTable from 'components/UploadsTable/UploadsTable'; -import WizardNavigation from 'components/Customer/WizardNavigation/WizardNavigation'; import FileUpload from 'components/FileUpload/FileUpload'; import { createUploadForAdditionalDocuments, deleteAdditionalDocumentUpload, getMove } from 'services/internalApi'; import { selectCurrentMove } from 'store/entities/selectors'; -import { customerRoutes } from 'constants/routes'; import { updateMove as updateMoveAction } from 'store/entities/actions'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; const AdditionalDocuments = ({ move, updateMove }) => { - const [isLoading, setIsLoading] = useState(true); const moveId = move?.id; const filePondEl = createRef(); const navigate = useNavigate(); - const [errorMessage, setErrorMessage] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const uploads = move?.additionalDocuments?.uploads; const handleDelete = async (uploadId) => { @@ -65,18 +61,6 @@ const AdditionalDocuments = ({ move, updateMove }) => { return ( - - - {errorMessage && ( - - - - {errorMessage} - - - - )} -

Additional Documents

diff --git a/swagger-def/internal.yaml b/swagger-def/internal.yaml index 191e79324fb..b7df9535d45 100644 --- a/swagger-def/internal.yaml +++ b/swagger-def/internal.yaml @@ -3077,7 +3077,7 @@ paths: name: moveId type: string format: uuid - description: ID of the move that the upload belongs to + description: Optional ID of the move that the upload belongs to responses: "204": description: deleted @@ -3703,10 +3703,10 @@ paths: $ref: "#/responses/UnprocessableEntity" "500": $ref: "#/responses/ServerError" - /moves/{moveId}/upload_additional_documents: + /moves/{moveId}/uploadAdditionalDocuments: patch: - summary: Patch the amended orders for a given order - description: Patch the amended orders for a given order + summary: Patch the additional documents for a given move + description: Customers will on occaision need the ability to upload additional supporting documents, for a variety of reasons. This does not include amended order. operationId: uploadAdditionalDocuments tags: - moves diff --git a/swagger/internal.yaml b/swagger/internal.yaml index 32484e25f30..90dfd218104 100644 --- a/swagger/internal.yaml +++ b/swagger/internal.yaml @@ -4368,7 +4368,7 @@ paths: name: moveId type: string format: uuid - description: ID of the move that the upload belongs to + description: Optional ID of the move that the upload belongs to responses: '204': description: deleted @@ -5007,10 +5007,13 @@ paths: $ref: '#/responses/UnprocessableEntity' '500': $ref: '#/responses/ServerError' - /moves/{moveId}/upload_additional_documents: + /moves/{moveId}/uploadAdditionalDocuments: patch: - summary: Patch the amended orders for a given order - description: Patch the amended orders for a given order + summary: Patch the additional documents for a given move + description: >- + Customers will on occaision need the ability to upload additional + supporting documents, for a variety of reasons. This does not include + amended order. operationId: uploadAdditionalDocuments tags: - moves From e7e98d5a45b9058c7d97574f0ed528dbd1b5e863 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Mon, 10 Jun 2024 17:06:25 +0000 Subject: [PATCH 08/10] bug fix for additional documents and current move --- src/constants/routes.js | 2 +- .../AdditionalDocuments.jsx | 55 ++++++++++++++----- src/pages/MyMove/Home/MoveHome.jsx | 2 +- 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/constants/routes.js b/src/constants/routes.js index 9655ccfeb15..f562c522d06 100644 --- a/src/constants/routes.js +++ b/src/constants/routes.js @@ -47,7 +47,7 @@ export const customerRoutes = { PROFILE_PATH: '/service-member/profile', SERVICE_INFO_EDIT_PATH: '/moves/review/edit-service-info', CONTACT_INFO_EDIT_PATH: '/moves/review/edit-contact-info', - UPLOAD_ADDITIONAL_DOCUMENTS_PATH: '/move/:moveLocator/upload-additional-documents', + UPLOAD_ADDITIONAL_DOCUMENTS_PATH: '/move/:moveId/upload-additional-documents', }; const BASE_COUNSELING_MOVE_PATH = '/counseling/moves/:moveCode'; diff --git a/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx b/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx index 53bce5648ee..35138d0ad8f 100644 --- a/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx +++ b/src/pages/MyMove/AdditionalDocuments/AdditionalDocuments.jsx @@ -1,24 +1,32 @@ import { React, createRef, useEffect, useState } from 'react'; import { GridContainer, Grid, Alert, Button } from '@trussworks/react-uswds'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, useParams } from 'react-router-dom'; import { connect } from 'react-redux'; import SectionWrapper from 'components/Customer/SectionWrapper'; import Hint from 'components/Hint'; import UploadsTable from 'components/UploadsTable/UploadsTable'; import FileUpload from 'components/FileUpload/FileUpload'; -import { createUploadForAdditionalDocuments, deleteAdditionalDocumentUpload, getMove } from 'services/internalApi'; -import { selectCurrentMove } from 'store/entities/selectors'; +import { + createUploadForAdditionalDocuments, + deleteAdditionalDocumentUpload, + getMove, + getResponseError, +} from 'services/internalApi'; +import { selectMovesForLoggedInUser } from 'store/entities/selectors'; import { updateMove as updateMoveAction } from 'store/entities/actions'; import LoadingPlaceholder from 'shared/LoadingPlaceholder'; +import scrollToTop from 'shared/scrollToTop'; +import NotificationScrollToTop from 'components/NotificationScrollToTop'; -const AdditionalDocuments = ({ move, updateMove }) => { - const moveId = move?.id; +const AdditionalDocuments = ({ moves, updateMove }) => { + const { moveId } = useParams(); const filePondEl = createRef(); const navigate = useNavigate(); const [isLoading, setIsLoading] = useState(true); - - const uploads = move?.additionalDocuments?.uploads; + const [serverError, setServerError] = useState(null); + const currentlyViewedMove = moves.find((move) => move.id === moveId); + const uploads = currentlyViewedMove?.additionalDocuments?.uploads; const handleDelete = async (uploadId) => { return deleteAdditionalDocumentUpload(uploadId, moveId).then(() => { @@ -33,9 +41,17 @@ const AdditionalDocuments = ({ move, updateMove }) => { }; const handleUploadComplete = () => { - getMove(moveId).then((res) => { - updateMove(res); - }); + getMove(moveId) + .then((res) => { + updateMove(res); + }) + .catch((e) => { + const { response } = e; + const error = getResponseError(response, 'failed to upload due to server error'); + setServerError(error); + + scrollToTop(); + }); }; const onChange = () => { @@ -61,6 +77,18 @@ const AdditionalDocuments = ({ move, updateMove }) => { return ( + + + {serverError && ( + + + + {serverError} + + + + )} +

Additional Documents

@@ -75,12 +103,10 @@ const AdditionalDocuments = ({ move, updateMove }) => {
Upload documents
PDF, JPG, or PNG only. Maximum file size 25MB. Each page must be clear and legible - {/* {uploads?.length > 0 && ( */} <>
- {/* )} */}
{ />
- {/* */}
@@ -100,9 +125,9 @@ const AdditionalDocuments = ({ move, updateMove }) => { }; const mapStateToProps = (state) => { - const move = selectCurrentMove(state); + const moves = selectMovesForLoggedInUser(state); - const props = { move }; + const props = { moves }; return props; }; diff --git a/src/pages/MyMove/Home/MoveHome.jsx b/src/pages/MyMove/Home/MoveHome.jsx index bc25cac591b..4736e88d060 100644 --- a/src/pages/MyMove/Home/MoveHome.jsx +++ b/src/pages/MyMove/Home/MoveHome.jsx @@ -415,7 +415,7 @@ const MoveHome = ({ serviceMemberMoves, isProfileComplete, serviceMember, signed const additionalDocumentsClick = () => { const uploadAdditionalDocumentsPath = generatePath(customerRoutes.UPLOAD_ADDITIONAL_DOCUMENTS_PATH, { - moveLocator: move.moveCode, + moveId: move.id, }); navigate(uploadAdditionalDocumentsPath, { state, moveId }); }; From 3245bec8d5d515c8dd6f994925adbfd630588e8b Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Mon, 10 Jun 2024 22:18:30 +0000 Subject: [PATCH 09/10] feature flag stuff for supporting docs --- .circleci/config.yml | 2 ++ config/env/demo.app-client-tls.env | 1 + config/env/demo.app.env | 1 + config/env/exp.app-client-tls.env | 1 + config/env/exp.app.env | 1 + config/env/loadtest.app-client-tls.env | 1 + config/env/loadtest.app.env | 1 + config/env/prd.app-client-tls.env | 1 + config/env/prd.app.env | 1 + config/env/stg.app-client-tls.env | 1 + config/env/stg.app.env | 1 + config/flipt/storage/development.features.yaml | 8 ++++++++ 12 files changed, 20 insertions(+) diff --git a/.circleci/config.yml b/.circleci/config.yml index b02db0ee0a0..b1d22d31292 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -871,6 +871,7 @@ commands: export FEATURE_FLAG_MOVE_LOCK=false export FEATURE_FLAG_OKTA_DODID_INPUT=false export FEATURE_FLAG_SAFETY_MOVE=false + export FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false # disable for speed, playwright tests can fail otherwise export DB_DEBUG=false @@ -905,6 +906,7 @@ commands: FEATURE_FLAG_MOVE_LOCK: 'false' FEATURE_FLAG_OKTA_DODID_INPUT: 'false' FEATURE_FLAG_SAFETY_MOVE: 'false' + FEATURE_FLAG_MANAGE_SUPPORTING_DOCS: 'false' command: | SHARD=$((${CIRCLE_NODE_INDEX}+1)) PLAYWRIGHT_JUNIT_OUTPUT_NAME=playwright-results.xml \ diff --git a/config/env/demo.app-client-tls.env b/config/env/demo.app-client-tls.env index bd057710fac..08d06a17154 100644 --- a/config/env/demo.app-client-tls.env +++ b/config/env/demo.app-client-tls.env @@ -39,3 +39,4 @@ FEATURE_FLAG_COAST_GUARD_EMPLID=false FEATURE_FLAG_MOVE_LOCK=false FEATURE_FLAG_OKTA_DODID_INPUT=false FEATURE_FLAG_SAFETY_MOVE=false +FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false diff --git a/config/env/demo.app.env b/config/env/demo.app.env index 1b65f312771..b862ee8b2ff 100644 --- a/config/env/demo.app.env +++ b/config/env/demo.app.env @@ -45,3 +45,4 @@ FEATURE_FLAG_COAST_GUARD_EMPLID=false FEATURE_FLAG_MOVE_LOCK=false FEATURE_FLAG_OKTA_DODID_INPUT=false FEATURE_FLAG_SAFETY_MOVE=false +FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false diff --git a/config/env/exp.app-client-tls.env b/config/env/exp.app-client-tls.env index c04d393f9c2..374d8703065 100644 --- a/config/env/exp.app-client-tls.env +++ b/config/env/exp.app-client-tls.env @@ -39,3 +39,4 @@ FEATURE_FLAG_COAST_GUARD_EMPLID=false FEATURE_FLAG_MOVE_LOCK=false FEATURE_FLAG_OKTA_DODID_INPUT=false FEATURE_FLAG_SAFETY_MOVE=false +FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false diff --git a/config/env/exp.app.env b/config/env/exp.app.env index cba66575f9b..fc28cfa0279 100644 --- a/config/env/exp.app.env +++ b/config/env/exp.app.env @@ -45,3 +45,4 @@ FEATURE_FLAG_COAST_GUARD_EMPLID=false FEATURE_FLAG_MOVE_LOCK=false FEATURE_FLAG_OKTA_DODID_INPUT=false FEATURE_FLAG_SAFETY_MOVE=false +FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false diff --git a/config/env/loadtest.app-client-tls.env b/config/env/loadtest.app-client-tls.env index e2af9a74216..75653d1c217 100644 --- a/config/env/loadtest.app-client-tls.env +++ b/config/env/loadtest.app-client-tls.env @@ -37,3 +37,4 @@ FEATURE_FLAG_COAST_GUARD_EMPLID=false FEATURE_FLAG_MOVE_LOCK=false FEATURE_FLAG_OKTA_DODID_INPUT=false FEATURE_FLAG_SAFETY_MOVE=false +FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false diff --git a/config/env/loadtest.app.env b/config/env/loadtest.app.env index a1c758914d1..229b4625373 100644 --- a/config/env/loadtest.app.env +++ b/config/env/loadtest.app.env @@ -43,3 +43,4 @@ FEATURE_FLAG_COAST_GUARD_EMPLID=false FEATURE_FLAG_MOVE_LOCK=false FEATURE_FLAG_OKTA_DODID_INPUT=false FEATURE_FLAG_SAFETY_MOVE=false +FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false diff --git a/config/env/prd.app-client-tls.env b/config/env/prd.app-client-tls.env index 0c5b60ee86f..3a1054eb475 100644 --- a/config/env/prd.app-client-tls.env +++ b/config/env/prd.app-client-tls.env @@ -36,3 +36,4 @@ FEATURE_FLAG_COAST_GUARD_EMPLID=false FEATURE_FLAG_MOVE_LOCK=false FEATURE_FLAG_OKTA_DODID_INPUT=false FEATURE_FLAG_SAFETY_MOVE=false +FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false diff --git a/config/env/prd.app.env b/config/env/prd.app.env index 21d4c9cadc4..4ca8b3bd756 100644 --- a/config/env/prd.app.env +++ b/config/env/prd.app.env @@ -44,3 +44,4 @@ FEATURE_FLAG_COAST_GUARD_EMPLID=false FEATURE_FLAG_MOVE_LOCK=false FEATURE_FLAG_OKTA_DODID_INPUT=false FEATURE_FLAG_SAFETY_MOVE=false +FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false diff --git a/config/env/stg.app-client-tls.env b/config/env/stg.app-client-tls.env index cda83e024f5..9b14e4d8028 100644 --- a/config/env/stg.app-client-tls.env +++ b/config/env/stg.app-client-tls.env @@ -38,3 +38,4 @@ FEATURE_FLAG_COAST_GUARD_EMPLID=false FEATURE_FLAG_MOVE_LOCK=false FEATURE_FLAG_OKTA_DODID_INPUT=false FEATURE_FLAG_SAFETY_MOVE=false +FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false diff --git a/config/env/stg.app.env b/config/env/stg.app.env index fdf5854d027..1f00064f3c0 100644 --- a/config/env/stg.app.env +++ b/config/env/stg.app.env @@ -45,3 +45,4 @@ FEATURE_FLAG_COAST_GUARD_EMPLID=false FEATURE_FLAG_MOVE_LOCK=false FEATURE_FLAG_OKTA_DODID_INPUT=false FEATURE_FLAG_SAFETY_MOVE=false +FEATURE_FLAG_MANAGE_SUPPORTING_DOCS=false diff --git a/config/flipt/storage/development.features.yaml b/config/flipt/storage/development.features.yaml index 1fe6457dc34..d5e32b50df8 100644 --- a/config/flipt/storage/development.features.yaml +++ b/config/flipt/storage/development.features.yaml @@ -89,6 +89,14 @@ flags: - segment: key: mil-app value: true +- key: manage_supporting_docs + name: Additional doc upload feature flag + type: BOOLEAN_FLAG_TYPE + enabled: false + rollouts: + - segment: + key: mil-app + value: false - key: boolean_flag name: Boolean Flag type: BOOLEAN_FLAG_TYPE From d263a148837b176f8e7ae2f163269bd0d6afa6a2 Mon Sep 17 00:00:00 2001 From: Paul Stonebraker Date: Tue, 11 Jun 2024 22:57:49 +0000 Subject: [PATCH 10/10] remove duplicate code --- pkg/handlers/internalapi/uploads.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/handlers/internalapi/uploads.go b/pkg/handlers/internalapi/uploads.go index e57c0673618..e168ee638a7 100644 --- a/pkg/handlers/internalapi/uploads.go +++ b/pkg/handlers/internalapi/uploads.go @@ -159,10 +159,6 @@ func (h DeleteUploadHandler) Handle(params uploadop.DeleteUploadParams) middlewa return handlers.ResponseForError(appCtx.Logger(), e), e } - if e != nil { - return handlers.ResponseForError(appCtx.Logger(), e), e - } - userUploader, e := uploaderpkg.NewUserUploader( h.FileStorer(), uploaderpkg.MaxCustomerUserUploadFileSizeLimit,