From 05cce0ee989d6eaaed0b473f9b209f3d67d819b2 Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 29 Aug 2024 18:30:07 +0000 Subject: [PATCH 01/10] added bulk download to Payment Request supporting documents --- pkg/gen/ghcapi/configure_mymove.go | 5 + pkg/gen/ghcapi/embedded_spec.go | 110 ++++++++ pkg/gen/ghcapi/ghcoperations/mymove_api.go | 12 + .../payment_requests/bulk_download.go | 58 ++++ .../bulk_download_parameters.go | 71 +++++ .../bulk_download_responses.go | 260 ++++++++++++++++++ .../bulk_download_urlbuilder.go | 99 +++++++ pkg/handlers/ghcapi/api.go | 6 + pkg/handlers/ghcapi/payment_request.go | 37 +++ pkg/handlers/ghcapi/ppm_document.go | 2 +- pkg/services/payment_request.go | 5 + .../payment_request_packed_creator.go | 57 ++++ .../PaymentRequestReview.jsx | 26 +- src/services/ghcApi.js | 4 + swagger-def/ghc.yaml | 34 +++ swagger/ghc.yaml | 35 +++ 16 files changed, 818 insertions(+), 3 deletions(-) create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go create mode 100644 pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go create mode 100644 pkg/services/payment_request/payment_request_packed_creator.go diff --git a/pkg/gen/ghcapi/configure_mymove.go b/pkg/gen/ghcapi/configure_mymove.go index 780c3432a1d..55761ad3124 100644 --- a/pkg/gen/ghcapi/configure_mymove.go +++ b/pkg/gen/ghcapi/configure_mymove.go @@ -94,6 +94,11 @@ func configureAPI(api *ghcoperations.MymoveAPI) http.Handler { return middleware.NotImplemented("operation report_violations.AssociateReportViolations has not yet been implemented") }) } + if api.PaymentRequestsBulkDownloadHandler == nil { + api.PaymentRequestsBulkDownloadHandler = payment_requests.BulkDownloadHandlerFunc(func(params payment_requests.BulkDownloadParams) middleware.Responder { + return middleware.NotImplemented("operation payment_requests.BulkDownload has not yet been implemented") + }) + } if api.OrderCounselingUpdateAllowanceHandler == nil { api.OrderCounselingUpdateAllowanceHandler = order.CounselingUpdateAllowanceHandlerFunc(func(params order.CounselingUpdateAllowanceParams) middleware.Responder { return middleware.NotImplemented("operation order.CounselingUpdateAllowance has not yet been implemented") diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index 9bf7560174f..a418e06a844 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -3121,6 +3121,55 @@ func init() { } ] }, + "/payment-requests/{paymentRequestID}/bulkDownload": { + "get": { + "description": "This endpoint downloads all uploaded payment request documentation combined into a single PDF.\n", + "produces": [ + "application/pdf" + ], + "tags": [ + "paymentRequests" + ], + "summary": "Downloads all Payment Request documents as a PDF", + "operationId": "bulkDownload", + "responses": { + "200": { + "description": "Payment Request Files PDF", + "schema": { + "type": "file", + "format": "binary" + }, + "headers": { + "Content-Disposition": { + "type": "string", + "description": "File name to download" + } + } + }, + "400": { + "$ref": "#/responses/InvalidRequest" + }, + "403": { + "$ref": "#/responses/PermissionDenied" + }, + "404": { + "$ref": "#/responses/NotFound" + }, + "500": { + "$ref": "#/responses/ServerError" + } + } + }, + "parameters": [ + { + "type": "string", + "description": "the id for the payment-request with files to be downloaded", + "name": "paymentRequestID", + "in": "path", + "required": true + } + ] + }, "/payment-requests/{paymentRequestID}/shipments-payment-sit-balance": { "get": { "description": "Returns all shipment payment request SIT usage to support partial SIT invoicing", @@ -17357,6 +17406,67 @@ func init() { } ] }, + "/payment-requests/{paymentRequestID}/bulkDownload": { + "get": { + "description": "This endpoint downloads all uploaded payment request documentation combined into a single PDF.\n", + "produces": [ + "application/pdf" + ], + "tags": [ + "paymentRequests" + ], + "summary": "Downloads all Payment Request documents as a PDF", + "operationId": "bulkDownload", + "responses": { + "200": { + "description": "Payment Request Files PDF", + "schema": { + "type": "file", + "format": "binary" + }, + "headers": { + "Content-Disposition": { + "type": "string", + "description": "File name to download" + } + } + }, + "400": { + "description": "The request payload is invalid", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "403": { + "description": "The request was denied", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "404": { + "description": "The requested resource wasn't found", + "schema": { + "$ref": "#/definitions/Error" + } + }, + "500": { + "description": "A server error occurred", + "schema": { + "$ref": "#/definitions/Error" + } + } + } + }, + "parameters": [ + { + "type": "string", + "description": "the id for the payment-request with files to be downloaded", + "name": "paymentRequestID", + "in": "path", + "required": true + } + ] + }, "/payment-requests/{paymentRequestID}/shipments-payment-sit-balance": { "get": { "description": "Returns all shipment payment request SIT usage to support partial SIT invoicing", diff --git a/pkg/gen/ghcapi/ghcoperations/mymove_api.go b/pkg/gen/ghcapi/ghcoperations/mymove_api.go index f5368032656..a528ad4222c 100644 --- a/pkg/gen/ghcapi/ghcoperations/mymove_api.go +++ b/pkg/gen/ghcapi/ghcoperations/mymove_api.go @@ -83,6 +83,9 @@ func NewMymoveAPI(spec *loads.Document) *MymoveAPI { ReportViolationsAssociateReportViolationsHandler: report_violations.AssociateReportViolationsHandlerFunc(func(params report_violations.AssociateReportViolationsParams) middleware.Responder { return middleware.NotImplemented("operation report_violations.AssociateReportViolations has not yet been implemented") }), + PaymentRequestsBulkDownloadHandler: payment_requests.BulkDownloadHandlerFunc(func(params payment_requests.BulkDownloadParams) middleware.Responder { + return middleware.NotImplemented("operation payment_requests.BulkDownload has not yet been implemented") + }), OrderCounselingUpdateAllowanceHandler: order.CounselingUpdateAllowanceHandlerFunc(func(params order.CounselingUpdateAllowanceParams) middleware.Responder { return middleware.NotImplemented("operation order.CounselingUpdateAllowance has not yet been implemented") }), @@ -406,6 +409,8 @@ type MymoveAPI struct { ShipmentApproveShipmentDiversionHandler shipment.ApproveShipmentDiversionHandler // ReportViolationsAssociateReportViolationsHandler sets the operation handler for the associate report violations operation ReportViolationsAssociateReportViolationsHandler report_violations.AssociateReportViolationsHandler + // PaymentRequestsBulkDownloadHandler sets the operation handler for the bulk download operation + PaymentRequestsBulkDownloadHandler payment_requests.BulkDownloadHandler // OrderCounselingUpdateAllowanceHandler sets the operation handler for the counseling update allowance operation OrderCounselingUpdateAllowanceHandler order.CounselingUpdateAllowanceHandler // OrderCounselingUpdateOrderHandler sets the operation handler for the counseling update order operation @@ -682,6 +687,9 @@ func (o *MymoveAPI) Validate() error { if o.ReportViolationsAssociateReportViolationsHandler == nil { unregistered = append(unregistered, "report_violations.AssociateReportViolationsHandler") } + if o.PaymentRequestsBulkDownloadHandler == nil { + unregistered = append(unregistered, "payment_requests.BulkDownloadHandler") + } if o.OrderCounselingUpdateAllowanceHandler == nil { unregistered = append(unregistered, "order.CounselingUpdateAllowanceHandler") } @@ -1061,6 +1069,10 @@ func (o *MymoveAPI) initHandlerCache() { o.handlers["POST"] = make(map[string]http.Handler) } o.handlers["POST"]["/report-violations/{reportID}"] = report_violations.NewAssociateReportViolations(o.context, o.ReportViolationsAssociateReportViolationsHandler) + if o.handlers["GET"] == nil { + o.handlers["GET"] = make(map[string]http.Handler) + } + o.handlers["GET"]["/payment-requests/{paymentRequestID}/bulkDownload"] = payment_requests.NewBulkDownload(o.context, o.PaymentRequestsBulkDownloadHandler) if o.handlers["PATCH"] == nil { o.handlers["PATCH"] = make(map[string]http.Handler) } diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go new file mode 100644 index 00000000000..78f7901ab05 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download.go @@ -0,0 +1,58 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// 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" +) + +// BulkDownloadHandlerFunc turns a function with the right signature into a bulk download handler +type BulkDownloadHandlerFunc func(BulkDownloadParams) middleware.Responder + +// Handle executing the request and returning a response +func (fn BulkDownloadHandlerFunc) Handle(params BulkDownloadParams) middleware.Responder { + return fn(params) +} + +// BulkDownloadHandler interface for that can handle valid bulk download params +type BulkDownloadHandler interface { + Handle(BulkDownloadParams) middleware.Responder +} + +// NewBulkDownload creates a new http.Handler for the bulk download operation +func NewBulkDownload(ctx *middleware.Context, handler BulkDownloadHandler) *BulkDownload { + return &BulkDownload{Context: ctx, Handler: handler} +} + +/* + BulkDownload swagger:route GET /payment-requests/{paymentRequestID}/bulkDownload paymentRequests bulkDownload + +# Downloads all Payment Request documents as a PDF + +This endpoint downloads all uploaded payment request documentation combined into a single PDF. +*/ +type BulkDownload struct { + Context *middleware.Context + Handler BulkDownloadHandler +} + +func (o *BulkDownload) ServeHTTP(rw http.ResponseWriter, r *http.Request) { + route, rCtx, _ := o.Context.RouteInfo(r) + if rCtx != nil { + *r = *rCtx + } + var Params = NewBulkDownloadParams() + if err := o.Context.BindValidRequest(r, route, &Params); err != nil { // bind params + o.Context.Respond(rw, r, route.Produces, route, err) + return + } + + res := o.Handler.Handle(Params) // actually handle the request + o.Context.Respond(rw, r, route.Produces, route, res) + +} diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go new file mode 100644 index 00000000000..ff67f05c88a --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_parameters.go @@ -0,0 +1,71 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "net/http" + + "github.com/go-openapi/errors" + "github.com/go-openapi/runtime/middleware" + "github.com/go-openapi/strfmt" +) + +// NewBulkDownloadParams creates a new BulkDownloadParams object +// +// There are no default values defined in the spec. +func NewBulkDownloadParams() BulkDownloadParams { + + return BulkDownloadParams{} +} + +// BulkDownloadParams contains all the bound params for the bulk download operation +// typically these are obtained from a http.Request +// +// swagger:parameters bulkDownload +type BulkDownloadParams struct { + + // HTTP Request Object + HTTPRequest *http.Request `json:"-"` + + /*the id for the payment-request with files to be downloaded + Required: true + In: path + */ + PaymentRequestID string +} + +// 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 NewBulkDownloadParams() beforehand. +func (o *BulkDownloadParams) BindRequest(r *http.Request, route *middleware.MatchedRoute) error { + var res []error + + o.HTTPRequest = r + + rPaymentRequestID, rhkPaymentRequestID, _ := route.Params.GetOK("paymentRequestID") + if err := o.bindPaymentRequestID(rPaymentRequestID, rhkPaymentRequestID, route.Formats); err != nil { + res = append(res, err) + } + if len(res) > 0 { + return errors.CompositeValidationError(res...) + } + return nil +} + +// bindPaymentRequestID binds and validates parameter PaymentRequestID from path. +func (o *BulkDownloadParams) bindPaymentRequestID(rawData []string, hasKey bool, formats strfmt.Registry) error { + var raw string + if len(rawData) > 0 { + raw = rawData[len(rawData)-1] + } + + // Required: true + // Parameter is provided by construction from the route + o.PaymentRequestID = raw + + return nil +} diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go new file mode 100644 index 00000000000..aa2aa194d11 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go @@ -0,0 +1,260 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// This file was generated by the swagger tool. +// Editing this file might prove futile when you re-run the swagger generate command + +import ( + "io" + "net/http" + + "github.com/go-openapi/runtime" + + "github.com/transcom/mymove/pkg/gen/ghcmessages" +) + +// BulkDownloadOKCode is the HTTP code returned for type BulkDownloadOK +const BulkDownloadOKCode int = 200 + +/* +BulkDownloadOK Payment Request Files PDF + +swagger:response bulkDownloadOK +*/ +type BulkDownloadOK struct { + /*File name to download + + */ + ContentDisposition string `json:"Content-Disposition"` + + /* + In: Body + */ + Payload io.ReadCloser `json:"body,omitempty"` +} + +// NewBulkDownloadOK creates BulkDownloadOK with default headers values +func NewBulkDownloadOK() *BulkDownloadOK { + + return &BulkDownloadOK{} +} + +// WithContentDisposition adds the contentDisposition to the bulk download o k response +func (o *BulkDownloadOK) WithContentDisposition(contentDisposition string) *BulkDownloadOK { + o.ContentDisposition = contentDisposition + return o +} + +// SetContentDisposition sets the contentDisposition to the bulk download o k response +func (o *BulkDownloadOK) SetContentDisposition(contentDisposition string) { + o.ContentDisposition = contentDisposition +} + +// WithPayload adds the payload to the bulk download o k response +func (o *BulkDownloadOK) WithPayload(payload io.ReadCloser) *BulkDownloadOK { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download o k response +func (o *BulkDownloadOK) SetPayload(payload io.ReadCloser) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadOK) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + // response header Content-Disposition + + contentDisposition := o.ContentDisposition + if contentDisposition != "" { + rw.Header().Set("Content-Disposition", contentDisposition) + } + + rw.WriteHeader(200) + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } +} + +// BulkDownloadBadRequestCode is the HTTP code returned for type BulkDownloadBadRequest +const BulkDownloadBadRequestCode int = 400 + +/* +BulkDownloadBadRequest The request payload is invalid + +swagger:response bulkDownloadBadRequest +*/ +type BulkDownloadBadRequest struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewBulkDownloadBadRequest creates BulkDownloadBadRequest with default headers values +func NewBulkDownloadBadRequest() *BulkDownloadBadRequest { + + return &BulkDownloadBadRequest{} +} + +// WithPayload adds the payload to the bulk download bad request response +func (o *BulkDownloadBadRequest) WithPayload(payload *ghcmessages.Error) *BulkDownloadBadRequest { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download bad request response +func (o *BulkDownloadBadRequest) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadBadRequest) 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 + } + } +} + +// BulkDownloadForbiddenCode is the HTTP code returned for type BulkDownloadForbidden +const BulkDownloadForbiddenCode int = 403 + +/* +BulkDownloadForbidden The request was denied + +swagger:response bulkDownloadForbidden +*/ +type BulkDownloadForbidden struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewBulkDownloadForbidden creates BulkDownloadForbidden with default headers values +func NewBulkDownloadForbidden() *BulkDownloadForbidden { + + return &BulkDownloadForbidden{} +} + +// WithPayload adds the payload to the bulk download forbidden response +func (o *BulkDownloadForbidden) WithPayload(payload *ghcmessages.Error) *BulkDownloadForbidden { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download forbidden response +func (o *BulkDownloadForbidden) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(403) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// BulkDownloadNotFoundCode is the HTTP code returned for type BulkDownloadNotFound +const BulkDownloadNotFoundCode int = 404 + +/* +BulkDownloadNotFound The requested resource wasn't found + +swagger:response bulkDownloadNotFound +*/ +type BulkDownloadNotFound struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewBulkDownloadNotFound creates BulkDownloadNotFound with default headers values +func NewBulkDownloadNotFound() *BulkDownloadNotFound { + + return &BulkDownloadNotFound{} +} + +// WithPayload adds the payload to the bulk download not found response +func (o *BulkDownloadNotFound) WithPayload(payload *ghcmessages.Error) *BulkDownloadNotFound { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download not found response +func (o *BulkDownloadNotFound) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(404) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} + +// BulkDownloadInternalServerErrorCode is the HTTP code returned for type BulkDownloadInternalServerError +const BulkDownloadInternalServerErrorCode int = 500 + +/* +BulkDownloadInternalServerError A server error occurred + +swagger:response bulkDownloadInternalServerError +*/ +type BulkDownloadInternalServerError struct { + + /* + In: Body + */ + Payload *ghcmessages.Error `json:"body,omitempty"` +} + +// NewBulkDownloadInternalServerError creates BulkDownloadInternalServerError with default headers values +func NewBulkDownloadInternalServerError() *BulkDownloadInternalServerError { + + return &BulkDownloadInternalServerError{} +} + +// WithPayload adds the payload to the bulk download internal server error response +func (o *BulkDownloadInternalServerError) WithPayload(payload *ghcmessages.Error) *BulkDownloadInternalServerError { + o.Payload = payload + return o +} + +// SetPayload sets the payload to the bulk download internal server error response +func (o *BulkDownloadInternalServerError) SetPayload(payload *ghcmessages.Error) { + o.Payload = payload +} + +// WriteResponse to the client +func (o *BulkDownloadInternalServerError) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { + + rw.WriteHeader(500) + if o.Payload != nil { + payload := o.Payload + if err := producer.Produce(rw, payload); err != nil { + panic(err) // let the recovery middleware deal with this + } + } +} diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go new file mode 100644 index 00000000000..7f67d894191 --- /dev/null +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_urlbuilder.go @@ -0,0 +1,99 @@ +// Code generated by go-swagger; DO NOT EDIT. + +package payment_requests + +// 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" +) + +// BulkDownloadURL generates an URL for the bulk download operation +type BulkDownloadURL struct { + PaymentRequestID string + + _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 *BulkDownloadURL) WithBasePath(bp string) *BulkDownloadURL { + 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 *BulkDownloadURL) SetBasePath(bp string) { + o._basePath = bp +} + +// Build a url path and query string +func (o *BulkDownloadURL) Build() (*url.URL, error) { + var _result url.URL + + var _path = "/payment-requests/{paymentRequestID}/bulkDownload" + + paymentRequestID := o.PaymentRequestID + if paymentRequestID != "" { + _path = strings.Replace(_path, "{paymentRequestID}", paymentRequestID, -1) + } else { + return nil, errors.New("paymentRequestId is required on BulkDownloadURL") + } + + _basePath := o._basePath + if _basePath == "" { + _basePath = "/ghc/v1" + } + _result.Path = golangswaggerpaths.Join(_basePath, _path) + + return &_result, nil +} + +// Must is a helper function to panic when the url builder returns an error +func (o *BulkDownloadURL) 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 *BulkDownloadURL) String() string { + return o.Must(o.Build()).String() +} + +// BuildFull builds a full url with scheme, host, path and query string +func (o *BulkDownloadURL) BuildFull(scheme, host string) (*url.URL, error) { + if scheme == "" { + return nil, errors.New("scheme is required for a full url on BulkDownloadURL") + } + if host == "" { + return nil, errors.New("host is required for a full url on BulkDownloadURL") + } + + 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 *BulkDownloadURL) StringFull(scheme, host string) string { + return o.Must(o.BuildFull(scheme, host)).String() +} diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 6cef37ab268..2b91ad51f8a 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -665,5 +665,11 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { order.NewOrderUpdater(moveRouter), } + paymentRequestPacketCreator := paymentrequest.NewPaymentRequestBulkDownloadCreator(pdfGenerator) + ghcAPI.PaymentRequestsBulkDownloadHandler = PaymentRequestBulkDownloadHandler{ + handlerConfig, + paymentRequestPacketCreator, + } + return ghcAPI } diff --git a/pkg/handlers/ghcapi/payment_request.go b/pkg/handlers/ghcapi/payment_request.go index 757993d2669..f2ede5a8d23 100644 --- a/pkg/handlers/ghcapi/payment_request.go +++ b/pkg/handlers/ghcapi/payment_request.go @@ -2,6 +2,7 @@ package ghcapi import ( "fmt" + "io" "reflect" "time" @@ -261,3 +262,39 @@ func (h ShipmentsSITBalanceHandler) Handle( return paymentrequestop.NewGetShipmentsPaymentSITBalanceOK().WithPayload(payload), nil }) } + +type PaymentRequestBulkDownloadHandler struct { + handlers.HandlerConfig + services.PaymentRequestPacketCreator +} + +func (h PaymentRequestBulkDownloadHandler) Handle(params paymentrequestop.BulkDownloadParams) middleware.Responder { + return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, + func(appCtx appcontext.AppContext) (middleware.Responder, error) { + logger := appCtx.Logger() + + paymentRequestID, err := uuid.FromString(params.PaymentRequestID) + if err != nil { + errInstance := fmt.Sprintf("Instance: %s", h.GetTraceIDFromRequest(params.HTTPRequest)) + + errPayload := &ghcmessages.Error{Message: &errInstance} + + appCtx.Logger().Error(err.Error()) + return paymentrequestop.NewBulkDownloadBadRequest().WithPayload(errPayload), err + } + + paymentRequestPacket, err := h.PaymentRequestPacketCreator.CreatePaymentRequestPacket(appCtx, paymentRequestID) + if err != nil { + logger.Error("Error creating Payment Request Downloads Packet", zap.Error(err)) + errInstance := fmt.Sprintf("Instance: %s", h.GetTraceIDFromRequest(params.HTTPRequest)) + errPayload := &ghcmessages.Error{Message: &errInstance} + return paymentrequestop.NewBulkDownloadInternalServerError(). + WithPayload(errPayload), err + } + + payload := io.NopCloser(paymentRequestPacket) + filename := fmt.Sprintf("inline; filename=\"PaymentRequestBulkPacket-%s.pdf\"", time.Now().Format("01-02-2006_15-04-05")) + + return paymentrequestop.NewBulkDownloadOK().WithContentDisposition(filename).WithPayload(payload), nil + }) +} diff --git a/pkg/handlers/ghcapi/ppm_document.go b/pkg/handlers/ghcapi/ppm_document.go index ee92f7e5d29..ba1bf703822 100644 --- a/pkg/handlers/ghcapi/ppm_document.go +++ b/pkg/handlers/ghcapi/ppm_document.go @@ -222,7 +222,7 @@ func (h ShowPaymentPacketHandler) Handle(params ppmdocumentops.ShowPaymentPacket } payload := io.NopCloser(pdf) - filename := fmt.Sprintf("inline; filename=\"ppm_payment_packet-%s.pdf\"", time.Now().UTC().Format("2006-01-02T15:04:05.000Z")) + filename := fmt.Sprintf("inline; filename=\"ppm_payment_bulk_download_packet-%s.pdf\"", time.Now().UTC().Format("2006-01-02T15:04:05.000Z")) return ppmdocumentops.NewShowPaymentPacketOK().WithContentDisposition(filename).WithPayload(payload), nil }) diff --git a/pkg/services/payment_request.go b/pkg/services/payment_request.go index 5f81f2215c9..2b10ab0bb17 100644 --- a/pkg/services/payment_request.go +++ b/pkg/services/payment_request.go @@ -5,6 +5,7 @@ import ( "time" "github.com/gofrs/uuid" + "github.com/spf13/afero" "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/models" @@ -115,3 +116,7 @@ type ShipmentPaymentSITBalance struct { type ShipmentsPaymentSITBalance interface { ListShipmentPaymentSITBalance(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) ([]ShipmentPaymentSITBalance, error) } + +type PaymentRequestPacketCreator interface { + CreatePaymentRequestPacket(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) +} diff --git a/pkg/services/payment_request/payment_request_packed_creator.go b/pkg/services/payment_request/payment_request_packed_creator.go new file mode 100644 index 00000000000..3d989413de9 --- /dev/null +++ b/pkg/services/payment_request/payment_request_packed_creator.go @@ -0,0 +1,57 @@ +package paymentrequest + +import ( + "fmt" + + "github.com/gofrs/uuid" + "github.com/spf13/afero" + + "github.com/transcom/mymove/pkg/appcontext" + "github.com/transcom/mymove/pkg/models" + "github.com/transcom/mymove/pkg/paperwork" + "github.com/transcom/mymove/pkg/services" +) + +type paymentRequestBulkDownloadCreator struct { + pdfGenerator *paperwork.Generator +} + +func NewPaymentRequestBulkDownloadCreator(pdfGenerator *paperwork.Generator) services.PaymentRequestPacketCreator { + return &paymentRequestBulkDownloadCreator{ + pdfGenerator, + } +} + +func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestPacket(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) { + errMsgPrefix := "error creating Payment Request packet" + + paymentRequest := models.PaymentRequest{} + err := appCtx.DB().Q().Eager( + "MoveTaskOrder", + "ProofOfServiceDocs", + "ProofOfServiceDocs.PrimeUploads", + "ProofOfServiceDocs.PrimeUploads.Upload", + ).Find(&paymentRequest, paymentRequestID) + if err != nil { + return nil, fmt.Errorf("%s: %w", errMsgPrefix, err) + } + + var primeUploads models.Uploads + for _, serviceDoc := range paymentRequest.ProofOfServiceDocs { + for _, upload := range serviceDoc.PrimeUploads { + primeUploads = append(primeUploads, upload.Upload) + } + } + + pdfs, err := p.pdfGenerator.ConvertUploadsToPDF(appCtx, primeUploads) + if err != nil { + return nil, fmt.Errorf("%s error generating pdf", err) + } + + pdfFile, err := p.pdfGenerator.MergePDFFiles(appCtx, pdfs) + if err != nil { + return nil, fmt.Errorf("%s error generating merged pdf", err) + } + + return pdfFile, nil +} diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx index e4a3f2c576f..041dcdeebb7 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx @@ -11,10 +11,11 @@ import SomethingWentWrong from 'shared/SomethingWentWrong'; import DocumentViewer from 'components/DocumentViewer/DocumentViewer'; import ReviewServiceItems from 'components/Office/ReviewServiceItems/ReviewServiceItems'; import { LOA_TYPE, PAYMENT_REQUEST_STATUS } from 'shared/constants'; -import { patchPaymentRequest, patchPaymentServiceItemStatus } from 'services/ghcApi'; +import { bulkDownloadPaymentRequest, patchPaymentRequest, patchPaymentServiceItemStatus } from 'services/ghcApi'; import { usePaymentRequestQueries } from 'hooks/queries'; import { PAYMENT_REQUESTS } from 'constants/queryKeys'; import { OrderShape } from 'types'; +import AsyncPacketDownloadLink from 'shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink'; export const PaymentRequestReview = ({ order }) => { const navigate = useNavigate(); @@ -172,10 +173,31 @@ export const PaymentRequestReview = ({ order }) => { navigate(`/moves/${moveCode}/payment-requests`); }; + const paymentPacketDownload = ( +
+
+

+ +

+
+
+ ); + return (
- {uploads.length > 0 ? :

No documents provided

} + {uploads.length > 0 ? ( + <> + {paymentPacketDownload} + + + ) : ( +

No documents provided

+ )}
+ This endpoint downloads all uploaded payment request documentation + combined into a single PDF. + operationId: bulkDownload + tags: + - paymentRequests + produces: + - application/pdf + responses: + '200': + headers: + Content-Disposition: + type: string + description: File name to download + description: Payment Request Files PDF + schema: + format: binary + type: file + '400': + $ref: '#/responses/InvalidRequest' + '403': + $ref: '#/responses/PermissionDenied' + '404': + $ref: '#/responses/NotFound' + '500': + $ref: '#/responses/ServerError' /documents/{documentId}: get: summary: Returns a document From eacfa51633f174012eb89aa2399eeb32a922dc69 Mon Sep 17 00:00:00 2001 From: loganwc Date: Fri, 30 Aug 2024 14:43:49 +0000 Subject: [PATCH 02/10] pdf generator does not rotate images anymore --- pkg/handlers/ghcapi/api.go | 6 +- pkg/handlers/ghcapi/payment_request.go | 4 +- pkg/handlers/ghcapi/ppm_document.go | 6 +- pkg/handlers/ghcapi/ppm_document_test.go | 6 +- pkg/paperwork/generator.go | 128 +++++++++++++++++- pkg/paperwork/generator_test.go | 61 ++++++++- pkg/services/payment_request.go | 4 +- ... payment_request_bulk_download_creator.go} | 6 +- ...ment_request_bulk_download_creator_test.go | 36 +++++ .../PaymentRequestReview.jsx | 2 +- .../PaymentRequestReview.test.jsx | 10 +- 11 files changed, 243 insertions(+), 26 deletions(-) rename pkg/services/payment_request/{payment_request_packed_creator.go => payment_request_bulk_download_creator.go} (89%) create mode 100644 pkg/services/payment_request/payment_request_bulk_download_creator_test.go diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 2b91ad51f8a..63db2004d53 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -637,7 +637,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { } ppmShipmentFetcher := ppmshipment.NewPPMShipmentFetcher() paymentPacketCreator := ppmshipment.NewPaymentPacketCreator(ppmShipmentFetcher, pdfGenerator, AOAPacketCreator) - ghcAPI.PpmShowPaymentPacketHandler = ShowPaymentPacketHandler{handlerConfig, paymentPacketCreator} + ghcAPI.PpmShowPaymentPacketHandler = ShowPaymentRequestBulkDownloadHandler{handlerConfig, paymentPacketCreator} ghcAPI.UploadsCreateUploadHandler = CreateUploadHandler{handlerConfig} ghcAPI.UploadsDeleteUploadHandler = DeleteUploadHandler{handlerConfig, upload.NewUploadInformationFetcher()} @@ -665,10 +665,10 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { order.NewOrderUpdater(moveRouter), } - paymentRequestPacketCreator := paymentrequest.NewPaymentRequestBulkDownloadCreator(pdfGenerator) + paymentRequestBulkDownloadCreator := paymentrequest.NewPaymentRequestBulkDownloadCreator(pdfGenerator) ghcAPI.PaymentRequestsBulkDownloadHandler = PaymentRequestBulkDownloadHandler{ handlerConfig, - paymentRequestPacketCreator, + paymentRequestBulkDownloadCreator, } return ghcAPI diff --git a/pkg/handlers/ghcapi/payment_request.go b/pkg/handlers/ghcapi/payment_request.go index f2ede5a8d23..622d38b1abc 100644 --- a/pkg/handlers/ghcapi/payment_request.go +++ b/pkg/handlers/ghcapi/payment_request.go @@ -265,7 +265,7 @@ func (h ShipmentsSITBalanceHandler) Handle( type PaymentRequestBulkDownloadHandler struct { handlers.HandlerConfig - services.PaymentRequestPacketCreator + services.PaymentRequestBulkDownloadCreator } func (h PaymentRequestBulkDownloadHandler) Handle(params paymentrequestop.BulkDownloadParams) middleware.Responder { @@ -283,7 +283,7 @@ func (h PaymentRequestBulkDownloadHandler) Handle(params paymentrequestop.BulkDo return paymentrequestop.NewBulkDownloadBadRequest().WithPayload(errPayload), err } - paymentRequestPacket, err := h.PaymentRequestPacketCreator.CreatePaymentRequestPacket(appCtx, paymentRequestID) + paymentRequestPacket, err := h.PaymentRequestBulkDownloadCreator.CreatePaymentRequestBulkDownload(appCtx, paymentRequestID) if err != nil { logger.Error("Error creating Payment Request Downloads Packet", zap.Error(err)) errInstance := fmt.Sprintf("Instance: %s", h.GetTraceIDFromRequest(params.HTTPRequest)) diff --git a/pkg/handlers/ghcapi/ppm_document.go b/pkg/handlers/ghcapi/ppm_document.go index ba1bf703822..32a9c021545 100644 --- a/pkg/handlers/ghcapi/ppm_document.go +++ b/pkg/handlers/ghcapi/ppm_document.go @@ -193,14 +193,14 @@ func (h showAOAPacketHandler) Handle(params ppmdocumentops.ShowAOAPacketParams) }) } -// ShowPaymentPacketHandler returns a PPM Payment Packet PDF -type ShowPaymentPacketHandler struct { +// ShowPaymentRequestBulkDownloadHandler returns a PPM Payment Packet PDF +type ShowPaymentRequestBulkDownloadHandler struct { handlers.HandlerConfig services.PaymentPacketCreator } // Handle returns a generated PDF -func (h ShowPaymentPacketHandler) Handle(params ppmdocumentops.ShowPaymentPacketParams) middleware.Responder { +func (h ShowPaymentRequestBulkDownloadHandler) Handle(params ppmdocumentops.ShowPaymentPacketParams) middleware.Responder { return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { ppmShipmentID, err := uuid.FromString(params.PpmShipmentID.String()) diff --git a/pkg/handlers/ghcapi/ppm_document_test.go b/pkg/handlers/ghcapi/ppm_document_test.go index 304a3b7a267..334a8d8af14 100644 --- a/pkg/handlers/ghcapi/ppm_document_test.go +++ b/pkg/handlers/ghcapi/ppm_document_test.go @@ -702,7 +702,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Successful ShowAOAPacketHandler - 200", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentPacketHandler{ + handler := ShowPaymentRequestBulkDownloadHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } @@ -728,7 +728,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Unsuccessful ShowPaymentPacketHandler - InternalServerError", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentPacketHandler{ + handler := ShowPaymentRequestBulkDownloadHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } @@ -754,7 +754,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Unsuccessful ShowPaymentPacketHandler - NotFoundError", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentPacketHandler{ + handler := ShowPaymentRequestBulkDownloadHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } diff --git a/pkg/paperwork/generator.go b/pkg/paperwork/generator.go index 881f58c454a..51b35e360a1 100644 --- a/pkg/paperwork/generator.go +++ b/pkg/paperwork/generator.go @@ -213,7 +213,7 @@ func (g *Generator) GetPdfFileInfoByContents(file afero.File) (*pdfcpu.PDFInfo, // CreateMergedPDFUpload converts Uploads to PDF and merges them into a single PDF func (g *Generator) CreateMergedPDFUpload(appCtx appcontext.AppContext, uploads models.Uploads) (afero.File, error) { - pdfs, err := g.ConvertUploadsToPDF(appCtx, uploads) + pdfs, err := g.ConvertUploadsToPDF(appCtx, uploads, true) if err != nil { return nil, errors.Wrap(err, "Error while converting uploads") } @@ -227,7 +227,7 @@ func (g *Generator) CreateMergedPDFUpload(appCtx appcontext.AppContext, uploads } // ConvertUploadsToPDF turns a slice of Uploads into a slice of paths to converted PDF files -func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads models.Uploads) ([]string, error) { +func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads models.Uploads, doRotation bool) ([]string, error) { // tempfile paths to be returned pdfs := make([]string, 0) @@ -240,9 +240,18 @@ func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads mo if len(images) > 0 { // We want to retain page order and will generate a PDF for images // that have already been encountered before handling this PDF. - pdf, err := g.PDFFromImages(appCtx, images) - if err != nil { - return nil, errors.Wrap(err, "Converting images") + var pdf string + var err error + if doRotation { + pdf, err = g.PDFFromImages(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting images") + } + } else { + pdf, err = g.PDFFromImagesNoRotation(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting images") + } } pdfs = append(pdfs, pdf) images = make([]inputFile, 0) @@ -514,6 +523,115 @@ func (g *Generator) PDFFromImages(appCtx appcontext.AppContext, images []inputFi return outputFile.Name(), nil } +// PDFFromImages returns the path to tempfile PDF containing all images included +// in urls. +// +// The files at those paths will be tempfiles that will need to be cleaned +// up by the caller. +func (g *Generator) PDFFromImagesNoRotation(appCtx appcontext.AppContext, images []inputFile) (string, error) { + // These constants are based on A4 page size, which we currently default to. + horizontalMargin := 0.0 + topMargin := 0.0 + bodyWidth := PdfPageWidth - (horizontalMargin * 2) + bodyHeight := PdfPageHeight - (topMargin * 2) + wToHRatio := bodyWidth / bodyHeight + + pdf := gofpdf.New(PdfOrientation, PdfUnit, PdfPageSize, PdfFontDir) + pdf.SetMargins(horizontalMargin, topMargin, horizontalMargin) + + if len(images) == 0 { + return "", errors.New("No images provided") + } + + appCtx.Logger().Debug("generating PDF from image files", zap.Any("images", images)) + + outputFile, err := g.newTempFile() + if err != nil { + return "", err + } + + defer func() { + if closeErr := outputFile.Close(); closeErr != nil { + appCtx.Logger().Debug("Failed to close file", zap.Error(closeErr)) + } + }() + + var opt gofpdf.ImageOptions + for _, img := range images { + pdf.AddPage() + file, openErr := g.fs.Open(img.Path) + if openErr != nil { + return "", errors.Wrap(openErr, "Opening image file") + } + + defer func() { + if closeErr := file.Close(); closeErr != nil { + appCtx.Logger().Debug("Failed to close file", zap.Error(closeErr)) + } + }() + + if img.ContentType == uploader.FileTypePNG { + appCtx.Logger().Debug("Converting png to 8-bit") + // gofpdf isn't able to process 16-bit PNGs, so to be safe we convert all PNGs to an 8-bit color depth + newFile, newTemplateFileErr := g.newTempFile() + if newTemplateFileErr != nil { + return "", errors.Wrap(newTemplateFileErr, "Creating temp file for png conversion") + } + + defer func() { + if closeErr := newFile.Close(); closeErr != nil { + appCtx.Logger().Debug("Failed to close file", zap.Error(closeErr)) + } + }() + + convertTo8BitPNGErr := convertTo8BitPNG(file, newFile) + if convertTo8BitPNGErr != nil { + return "", errors.Wrap(convertTo8BitPNGErr, "Converting to 8-bit png") + } + file = newFile + _, fileSeekErr := file.Seek(0, io.SeekStart) + if fileSeekErr != nil { + return "", errors.Wrapf(fileSeekErr, "file.Seek offset: 0 whence: %d", io.SeekStart) + } + } + + widthInPdf := bodyWidth + heightInPdf := 0.0 + + // Scale using the imageOptions below + // BodyWidth should be set to 0 when the image height the proportion of the page + // is taller than wide as compared to an A4 page. + // + // The opposite is true and defaulted for when the image is wider than it is tall, + // in comparison to an A4 page. + if float64(bodyWidth/bodyHeight) < wToHRatio { + widthInPdf = 0 + heightInPdf = bodyHeight + } + + // Seek to the beginning of the file so when we register the image, it doesn't start + // at the end of the file. + _, fileSeekErr := file.Seek(0, io.SeekStart) + if fileSeekErr != nil { + return "", errors.Wrapf(fileSeekErr, "file.Seek offset: 0 whence: %d", io.SeekStart) + } + // Need to register the image using an afero reader, else it uses default filesystem + pdf.RegisterImageReader(img.Path, contentTypeToImageType[img.ContentType], file) + opt.ImageType = contentTypeToImageType[img.ContentType] + + pdf.ImageOptions(img.Path, horizontalMargin, topMargin, widthInPdf, heightInPdf, false, opt, 0, "") + fileCloseErr := file.Close() + if fileCloseErr != nil { + return "", errors.Wrapf(err, "error closing file: %s", file.Name()) + } + } + + if err = pdf.OutputAndClose(outputFile); err != nil { + return "", errors.Wrap(err, "could not write PDF to outputfile") + } + return outputFile.Name(), nil +} + // MergePDFFiles Merges a slice of paths to PDF files into a single PDF func (g *Generator) MergePDFFiles(_ appcontext.AppContext, paths []string) (afero.File, error) { var err error diff --git a/pkg/paperwork/generator_test.go b/pkg/paperwork/generator_test.go index 8b0eddea933..775c3207696 100644 --- a/pkg/paperwork/generator_test.go +++ b/pkg/paperwork/generator_test.go @@ -143,6 +143,65 @@ func (suite *PaperworkSuite) TestPDFFromImages() { suite.Contains(checksums, orders2Checksum, "did not find hash for orders2.jpg") } +func (suite *PaperworkSuite) TestPDFFromImagesNoRotation() { + generator, newGeneratorErr := NewGenerator(suite.userUploader.Uploader()) + suite.FatalNil(newGeneratorErr) + + images := []inputFile{ + {Path: "testdata/orders1.jpg", ContentType: uploader.FileTypeJPEG}, + {Path: "testdata/orders2.jpg", ContentType: uploader.FileTypeJPEG}, + } + for _, image := range images { + _, err := suite.openLocalFile(image.Path, generator.fs) + suite.FatalNil(err) + } + + generatedPath, err := generator.PDFFromImagesNoRotation(suite.AppContextForTest(), images) + suite.FatalNil(err, "failed to generate pdf") + aferoFile, err := generator.fs.Open(generatedPath) + suite.FatalNil(err, "afero failed to open pdf") + + suite.NotEmpty(generatedPath, "got an empty path to the generated file") + suite.FatalNil(err) + + // verify that the images are in the pdf by extracting them and checking their checksums + file, err := afero.ReadAll(aferoFile) + suite.FatalNil(err) + tmpDir, err := os.MkdirTemp("", "images") + suite.FatalNil(err) + f, err := os.CreateTemp(tmpDir, "") + suite.FatalNil(err) + err = os.WriteFile(f.Name(), file, os.ModePerm) + suite.FatalNil(err) + err = api.ExtractImagesFile(f.Name(), tmpDir, []string{"-2"}, generator.pdfConfig) + suite.FatalNil(err) + err = os.Remove(f.Name()) + suite.FatalNil(err) + + checksums := make([]string, 2) + files, err := os.ReadDir(tmpDir) + suite.FatalNil(err) + + suite.Equal(4, len(files), "did not find 2 images") + + for _, file := range files { + checksum, sha256ForPathErr := suite.sha256ForPath(path.Join(tmpDir, file.Name()), nil) + suite.FatalNil(sha256ForPathErr, "error calculating hash") + if sha256ForPathErr != nil { + suite.FailNow(sha256ForPathErr.Error()) + } + checksums = append(checksums, checksum) + } + + orders1Checksum, err := suite.sha256ForPath("testdata/orders1.jpg", generator.fs) + suite.Nil(err, "error calculating hash") + suite.Contains(checksums, orders1Checksum, "did not find hash for orders1.jpg") + + orders2Checksum, err := suite.sha256ForPath("testdata/orders2.jpg", generator.fs) + suite.Nil(err, "error calculating hash") + suite.Contains(checksums, orders2Checksum, "did not find hash for orders2.jpg") +} + func (suite *PaperworkSuite) TestPDFFromImages16BitPNG() { generator, err := NewGenerator(suite.userUploader.Uploader()) suite.FatalNil(err) @@ -187,7 +246,7 @@ func (suite *PaperworkSuite) TestGenerateUploadsPDF() { uploads, err := models.UploadsFromUserUploads(suite.DB(), order.UploadedOrders.UserUploads) suite.FatalNil(err) - paths, err := generator.ConvertUploadsToPDF(suite.AppContextForTest(), uploads) + paths, err := generator.ConvertUploadsToPDF(suite.AppContextForTest(), uploads, true) suite.FatalNil(err) suite.Equal(3, len(paths), "wrong number of paths returned") diff --git a/pkg/services/payment_request.go b/pkg/services/payment_request.go index 2b10ab0bb17..25f62be43a4 100644 --- a/pkg/services/payment_request.go +++ b/pkg/services/payment_request.go @@ -117,6 +117,6 @@ type ShipmentsPaymentSITBalance interface { ListShipmentPaymentSITBalance(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) ([]ShipmentPaymentSITBalance, error) } -type PaymentRequestPacketCreator interface { - CreatePaymentRequestPacket(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) +type PaymentRequestBulkDownloadCreator interface { + CreatePaymentRequestBulkDownload(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) } diff --git a/pkg/services/payment_request/payment_request_packed_creator.go b/pkg/services/payment_request/payment_request_bulk_download_creator.go similarity index 89% rename from pkg/services/payment_request/payment_request_packed_creator.go rename to pkg/services/payment_request/payment_request_bulk_download_creator.go index 3d989413de9..24fb7d590c7 100644 --- a/pkg/services/payment_request/payment_request_packed_creator.go +++ b/pkg/services/payment_request/payment_request_bulk_download_creator.go @@ -16,13 +16,13 @@ type paymentRequestBulkDownloadCreator struct { pdfGenerator *paperwork.Generator } -func NewPaymentRequestBulkDownloadCreator(pdfGenerator *paperwork.Generator) services.PaymentRequestPacketCreator { +func NewPaymentRequestBulkDownloadCreator(pdfGenerator *paperwork.Generator) services.PaymentRequestBulkDownloadCreator { return &paymentRequestBulkDownloadCreator{ pdfGenerator, } } -func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestPacket(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) { +func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestBulkDownload(appCtx appcontext.AppContext, paymentRequestID uuid.UUID) (afero.File, error) { errMsgPrefix := "error creating Payment Request packet" paymentRequest := models.PaymentRequest{} @@ -43,7 +43,7 @@ func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestPacket(appCtx ap } } - pdfs, err := p.pdfGenerator.ConvertUploadsToPDF(appCtx, primeUploads) + pdfs, err := p.pdfGenerator.ConvertUploadsToPDF(appCtx, primeUploads, false) if err != nil { return nil, fmt.Errorf("%s error generating pdf", err) } diff --git a/pkg/services/payment_request/payment_request_bulk_download_creator_test.go b/pkg/services/payment_request/payment_request_bulk_download_creator_test.go new file mode 100644 index 00000000000..9db695dd54a --- /dev/null +++ b/pkg/services/payment_request/payment_request_bulk_download_creator_test.go @@ -0,0 +1,36 @@ +package paymentrequest + +import ( + "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/models" + paperworkgenerator "github.com/transcom/mymove/pkg/paperwork" + storageTest "github.com/transcom/mymove/pkg/storage/test" + "github.com/transcom/mymove/pkg/uploader" +) + +func (suite *PaymentRequestServiceSuite) TestCreatePaymentRequestBulkDownload() { + fakeS3 := storageTest.NewFakeS3Storage(true) + userUploader, uploaderErr := uploader.NewUserUploader(fakeS3, 25*uploader.MB) + suite.FatalNoError(uploaderErr) + + generator, err := paperworkgenerator.NewGenerator(userUploader.Uploader()) + suite.FatalNil(err) + + primeUpload := factory.BuildPrimeUpload(suite.DB(), nil, nil) + suite.FatalNil(err) + if generator != nil { + suite.FatalNil(err) + } + + paymentRequest := factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + { + Model: models.PaymentRequest{ + ProofOfServiceDocs: models.ProofOfServiceDocs{ + primeUpload.ProofOfServiceDoc, + }, + }, + }, + }, nil) + + suite.NotNil(paymentRequest) +} diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx index 041dcdeebb7..ea2e1ca1c1b 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.jsx @@ -175,7 +175,7 @@ export const PaymentRequestReview = ({ order }) => { const paymentPacketDownload = (
-
+

{ expect(terms[1]).toHaveTextContent('Accepted'); expect(terms[2]).toHaveTextContent('Rejected'); const definitions = screen.getAllByRole('definition'); - expect(definitions[0]).toHaveTextContent('$1,703.10'); - expect(definitions[1]).toHaveTextContent('$1,579.98'); - expect(definitions[2]).toHaveTextContent('$123.12'); + expect(definitions[1]).toHaveTextContent('$1,703.10'); + expect(definitions[2]).toHaveTextContent('$1,579.98'); + expect(definitions[3]).toHaveTextContent('$123.12'); }); it('navigates back, and shows the correct icons for approved and rejected cards', async () => { await userEvent.click(screen.getByRole('button', { name: 'Back' })); @@ -497,6 +497,10 @@ describe('PaymentRequestReview', () => { await userEvent.click(screen.getByRole('button', { name: 'Previous Service Item' })); expect(screen.getByTestId('statusHeading')).toHaveTextContent('Accepted'); }); + it('shows the Download All Files (PDF) button and downloads the pdf', async () => { + expect(screen.getByText('Download All Files (PDF)')).toBeInTheDocument(); + await userEvent.click(screen.getByText('Download All Files (PDF)')); + }); }); }); }); From 8d2504892355a072dc88225551ada8e66c7b69d6 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 3 Sep 2024 21:32:14 +0000 Subject: [PATCH 03/10] more work on test, still failing --- ...ment_request_bulk_download_creator_test.go | 45 +++++++++++-------- .../payment_request_service_test.go | 27 ++++++++++- 2 files changed, 52 insertions(+), 20 deletions(-) diff --git a/pkg/services/payment_request/payment_request_bulk_download_creator_test.go b/pkg/services/payment_request/payment_request_bulk_download_creator_test.go index 9db695dd54a..fd1c967d091 100644 --- a/pkg/services/payment_request/payment_request_bulk_download_creator_test.go +++ b/pkg/services/payment_request/payment_request_bulk_download_creator_test.go @@ -2,35 +2,42 @@ package paymentrequest import ( "github.com/transcom/mymove/pkg/factory" - "github.com/transcom/mymove/pkg/models" paperworkgenerator "github.com/transcom/mymove/pkg/paperwork" - storageTest "github.com/transcom/mymove/pkg/storage/test" "github.com/transcom/mymove/pkg/uploader" ) func (suite *PaymentRequestServiceSuite) TestCreatePaymentRequestBulkDownload() { - fakeS3 := storageTest.NewFakeS3Storage(true) - userUploader, uploaderErr := uploader.NewUserUploader(fakeS3, 25*uploader.MB) - suite.FatalNoError(uploaderErr) + primeUploader, err := uploader.NewPrimeUploader(suite.storer, 25*uploader.MB) + suite.NoError(err) - generator, err := paperworkgenerator.NewGenerator(userUploader.Uploader()) + generator, err := paperworkgenerator.NewGenerator(primeUploader.Uploader()) suite.FatalNil(err) - primeUpload := factory.BuildPrimeUpload(suite.DB(), nil, nil) - suite.FatalNil(err) - if generator != nil { - suite.FatalNil(err) - } - - paymentRequest := factory.BuildPaymentRequest(suite.DB(), []factory.Customization{ + paymentRequest := factory.BuildPaymentRequest(suite.DB(), nil, nil) + primeUpload := factory.BuildPrimeUpload(suite.DB(), []factory.Customization{ { - Model: models.PaymentRequest{ - ProofOfServiceDocs: models.ProofOfServiceDocs{ - primeUpload.ProofOfServiceDoc, - }, - }, + Model: paymentRequest, + LinkOnly: true, }, }, nil) + posd := factory.BuildProofOfServiceDoc(suite.DB(), []factory.Customization{ + { + Model: primeUpload, + LinkOnly: true, + }, + { + Model: paymentRequest, + LinkOnly: true, + }, + }, nil) + + paymentRequest.ProofOfServiceDocs = append(paymentRequest.ProofOfServiceDocs, posd) + + creator := &paymentRequestBulkDownloadCreator{ + pdfGenerator: generator, + } - suite.NotNil(paymentRequest) + bulkDownload, err := creator.CreatePaymentRequestBulkDownload(suite.AppContextForTest(), paymentRequest.ID) + suite.NoError(err) + suite.NotNil(bulkDownload) } diff --git a/pkg/services/payment_request/payment_request_service_test.go b/pkg/services/payment_request/payment_request_service_test.go index f13384719a9..37864593901 100644 --- a/pkg/services/payment_request/payment_request_service_test.go +++ b/pkg/services/payment_request/payment_request_service_test.go @@ -1,18 +1,24 @@ package paymentrequest import ( + "io" + "os" + "path/filepath" "testing" "github.com/spf13/afero" "github.com/stretchr/testify/suite" + "go.uber.org/zap" + "github.com/transcom/mymove/pkg/storage" "github.com/transcom/mymove/pkg/testingsuite" ) // PaymentRequestServiceSuite is a suite for testing payment requests type PaymentRequestServiceSuite struct { *testingsuite.PopTestSuite - fs *afero.Afero + fs *afero.Afero + storer storage.FileStorer } func TestPaymentRequestServiceSuite(t *testing.T) { @@ -25,3 +31,22 @@ func TestPaymentRequestServiceSuite(t *testing.T) { suite.Run(t, ts) ts.PopTestSuite.TearDown() } + +func (suite *PaymentRequestServiceSuite) openLocalFile(path string) (afero.File, error) { + file, err := os.Open(filepath.Clean(path)) + if err != nil { + suite.Logger().Fatal("Error opening local file", zap.Error(err)) + } + + outputFile, err := suite.fs.Create(path) + if err != nil { + suite.Logger().Fatal("Error creating afero file", zap.Error(err)) + } + + _, err = io.Copy(outputFile, file) + if err != nil { + suite.Logger().Fatal("Error copying to afero file", zap.Error(err)) + } + + return outputFile, nil +} From 8a24dab16c8d750c39da6dac1717fbbe829fc6d6 Mon Sep 17 00:00:00 2001 From: loganwc Date: Wed, 4 Sep 2024 20:01:45 -0500 Subject: [PATCH 04/10] added frontend test --- pkg/paperwork/generator.go | 16 +++++-- .../payment_request_bulk_download_creator.go | 2 +- ...ment_request_bulk_download_creator_test.go | 43 ------------------- .../payment_request_service_test.go | 27 +----------- .../PaymentRequestReview.test.jsx | 31 ++++++++++++- 5 files changed, 45 insertions(+), 74 deletions(-) delete mode 100644 pkg/services/payment_request/payment_request_bulk_download_creator_test.go diff --git a/pkg/paperwork/generator.go b/pkg/paperwork/generator.go index 51b35e360a1..23ee0accdc3 100644 --- a/pkg/paperwork/generator.go +++ b/pkg/paperwork/generator.go @@ -291,9 +291,19 @@ func (g *Generator) ConvertUploadsToPDF(appCtx appcontext.AppContext, uploads mo // Merge all remaining images in urls into a new PDF if len(images) > 0 { - pdf, err := g.PDFFromImages(appCtx, images) - if err != nil { - return nil, errors.Wrap(err, "Converting remaining images to pdf") + var pdf string + var err error + + if doRotation { + pdf, err = g.PDFFromImages(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting remaining images to pdf") + } + } else { + pdf, err = g.PDFFromImagesNoRotation(appCtx, images) + if err != nil { + return nil, errors.Wrap(err, "Converting remaining images to pdf") + } } pdfs = append(pdfs, pdf) } diff --git a/pkg/services/payment_request/payment_request_bulk_download_creator.go b/pkg/services/payment_request/payment_request_bulk_download_creator.go index 24fb7d590c7..1178f4c991c 100644 --- a/pkg/services/payment_request/payment_request_bulk_download_creator.go +++ b/pkg/services/payment_request/payment_request_bulk_download_creator.go @@ -32,7 +32,7 @@ func (p *paymentRequestBulkDownloadCreator) CreatePaymentRequestBulkDownload(app "ProofOfServiceDocs.PrimeUploads", "ProofOfServiceDocs.PrimeUploads.Upload", ).Find(&paymentRequest, paymentRequestID) - if err != nil { + if err != nil || len(paymentRequest.ProofOfServiceDocs) < 1 { return nil, fmt.Errorf("%s: %w", errMsgPrefix, err) } diff --git a/pkg/services/payment_request/payment_request_bulk_download_creator_test.go b/pkg/services/payment_request/payment_request_bulk_download_creator_test.go deleted file mode 100644 index fd1c967d091..00000000000 --- a/pkg/services/payment_request/payment_request_bulk_download_creator_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package paymentrequest - -import ( - "github.com/transcom/mymove/pkg/factory" - paperworkgenerator "github.com/transcom/mymove/pkg/paperwork" - "github.com/transcom/mymove/pkg/uploader" -) - -func (suite *PaymentRequestServiceSuite) TestCreatePaymentRequestBulkDownload() { - primeUploader, err := uploader.NewPrimeUploader(suite.storer, 25*uploader.MB) - suite.NoError(err) - - generator, err := paperworkgenerator.NewGenerator(primeUploader.Uploader()) - suite.FatalNil(err) - - paymentRequest := factory.BuildPaymentRequest(suite.DB(), nil, nil) - primeUpload := factory.BuildPrimeUpload(suite.DB(), []factory.Customization{ - { - Model: paymentRequest, - LinkOnly: true, - }, - }, nil) - posd := factory.BuildProofOfServiceDoc(suite.DB(), []factory.Customization{ - { - Model: primeUpload, - LinkOnly: true, - }, - { - Model: paymentRequest, - LinkOnly: true, - }, - }, nil) - - paymentRequest.ProofOfServiceDocs = append(paymentRequest.ProofOfServiceDocs, posd) - - creator := &paymentRequestBulkDownloadCreator{ - pdfGenerator: generator, - } - - bulkDownload, err := creator.CreatePaymentRequestBulkDownload(suite.AppContextForTest(), paymentRequest.ID) - suite.NoError(err) - suite.NotNil(bulkDownload) -} diff --git a/pkg/services/payment_request/payment_request_service_test.go b/pkg/services/payment_request/payment_request_service_test.go index 37864593901..f13384719a9 100644 --- a/pkg/services/payment_request/payment_request_service_test.go +++ b/pkg/services/payment_request/payment_request_service_test.go @@ -1,24 +1,18 @@ package paymentrequest import ( - "io" - "os" - "path/filepath" "testing" "github.com/spf13/afero" "github.com/stretchr/testify/suite" - "go.uber.org/zap" - "github.com/transcom/mymove/pkg/storage" "github.com/transcom/mymove/pkg/testingsuite" ) // PaymentRequestServiceSuite is a suite for testing payment requests type PaymentRequestServiceSuite struct { *testingsuite.PopTestSuite - fs *afero.Afero - storer storage.FileStorer + fs *afero.Afero } func TestPaymentRequestServiceSuite(t *testing.T) { @@ -31,22 +25,3 @@ func TestPaymentRequestServiceSuite(t *testing.T) { suite.Run(t, ts) ts.PopTestSuite.TearDown() } - -func (suite *PaymentRequestServiceSuite) openLocalFile(path string) (afero.File, error) { - file, err := os.Open(filepath.Clean(path)) - if err != nil { - suite.Logger().Fatal("Error opening local file", zap.Error(err)) - } - - outputFile, err := suite.fs.Create(path) - if err != nil { - suite.Logger().Fatal("Error creating afero file", zap.Error(err)) - } - - _, err = io.Copy(outputFile, file) - if err != nil { - suite.Logger().Fatal("Error copying to afero file", zap.Error(err)) - } - - return outputFile, nil -} diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx index 3874929fdfd..89727aacda2 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx @@ -6,7 +6,7 @@ import userEvent from '@testing-library/user-event'; import { PaymentRequestReview } from './PaymentRequestReview'; -import { patchPaymentServiceItemStatus } from 'services/ghcApi'; +import { patchPaymentServiceItemStatus, bulkDownloadPaymentRequest } from 'services/ghcApi'; import { SHIPMENT_OPTIONS, PAYMENT_REQUEST_STATUS, PAYMENT_SERVICE_ITEM_STATUS } from 'shared/constants'; import { usePaymentRequestQueries } from 'hooks/queries'; import { ReactQueryWrapper } from 'testUtils'; @@ -57,6 +57,7 @@ jest.mock('hooks/queries', () => ({ jest.mock('services/ghcApi', () => ({ ...jest.requireActual('services/ghcApi'), patchPaymentServiceItemStatus: jest.fn(), + bulkDownloadPaymentRequest: jest.fn(), })); // prevents react-fileviewer from throwing errors without mocking relevant DOM elements @@ -353,6 +354,34 @@ describe('PaymentRequestReview', () => { expect(reviewServiceItems.prop('serviceItemCards')).toEqual(expectedServiceItemCards); }); }); + describe('when clicking download Download All Files button', () => { + it('downloads a bulk packet', async () => { + usePaymentRequestQueries.mockReturnValue(usePaymentRequestQueriesReturnValuePendingFinalReview); + + const mockResponse = { + ok: true, + headers: { + 'content-disposition': 'filename="test.pdf"', + }, + status: 200, + data: null, + }; + + render( + + + , + ); + + bulkDownloadPaymentRequest.mockImplementation(() => Promise.resolve(mockResponse)); + + const downloadButton = screen.getByText('Download All Files (PDF)', { exact: false }); + await userEvent.click(downloadButton); + await waitFor(() => { + expect(bulkDownloadPaymentRequest).toHaveBeenCalledTimes(1); + }); + }); + }); describe('clicking the next button', () => { describe('with pending requests', () => { beforeEach(async () => { From f233e8c8e3426ea45c8321d68346bb44cd3601ca Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 5 Sep 2024 01:23:16 +0000 Subject: [PATCH 05/10] revert changes I didn't mean to make --- pkg/handlers/ghcapi/api.go | 2 +- pkg/handlers/ghcapi/ppm_document.go | 8 ++++---- pkg/handlers/ghcapi/ppm_document_test.go | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 6de9b1d5626..2fa8b0242d1 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -638,7 +638,7 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { } ppmShipmentFetcher := ppmshipment.NewPPMShipmentFetcher() paymentPacketCreator := ppmshipment.NewPaymentPacketCreator(ppmShipmentFetcher, pdfGenerator, AOAPacketCreator) - ghcAPI.PpmShowPaymentPacketHandler = ShowPaymentRequestBulkDownloadHandler{handlerConfig, paymentPacketCreator} + ghcAPI.PpmShowPaymentPacketHandler = ShowPaymentPacketHandler{handlerConfig, paymentPacketCreator} ghcAPI.UploadsCreateUploadHandler = CreateUploadHandler{handlerConfig} ghcAPI.UploadsUpdateUploadHandler = UpdateUploadHandler{handlerConfig, upload.NewUploadInformationFetcher()} diff --git a/pkg/handlers/ghcapi/ppm_document.go b/pkg/handlers/ghcapi/ppm_document.go index 32a9c021545..ee92f7e5d29 100644 --- a/pkg/handlers/ghcapi/ppm_document.go +++ b/pkg/handlers/ghcapi/ppm_document.go @@ -193,14 +193,14 @@ func (h showAOAPacketHandler) Handle(params ppmdocumentops.ShowAOAPacketParams) }) } -// ShowPaymentRequestBulkDownloadHandler returns a PPM Payment Packet PDF -type ShowPaymentRequestBulkDownloadHandler struct { +// ShowPaymentPacketHandler returns a PPM Payment Packet PDF +type ShowPaymentPacketHandler struct { handlers.HandlerConfig services.PaymentPacketCreator } // Handle returns a generated PDF -func (h ShowPaymentRequestBulkDownloadHandler) Handle(params ppmdocumentops.ShowPaymentPacketParams) middleware.Responder { +func (h ShowPaymentPacketHandler) Handle(params ppmdocumentops.ShowPaymentPacketParams) middleware.Responder { return h.AuditableAppContextFromRequestWithErrors(params.HTTPRequest, func(appCtx appcontext.AppContext) (middleware.Responder, error) { ppmShipmentID, err := uuid.FromString(params.PpmShipmentID.String()) @@ -222,7 +222,7 @@ func (h ShowPaymentRequestBulkDownloadHandler) Handle(params ppmdocumentops.Show } payload := io.NopCloser(pdf) - filename := fmt.Sprintf("inline; filename=\"ppm_payment_bulk_download_packet-%s.pdf\"", time.Now().UTC().Format("2006-01-02T15:04:05.000Z")) + filename := fmt.Sprintf("inline; filename=\"ppm_payment_packet-%s.pdf\"", time.Now().UTC().Format("2006-01-02T15:04:05.000Z")) return ppmdocumentops.NewShowPaymentPacketOK().WithContentDisposition(filename).WithPayload(payload), nil }) diff --git a/pkg/handlers/ghcapi/ppm_document_test.go b/pkg/handlers/ghcapi/ppm_document_test.go index 914eb7defe5..576a792f43c 100644 --- a/pkg/handlers/ghcapi/ppm_document_test.go +++ b/pkg/handlers/ghcapi/ppm_document_test.go @@ -702,7 +702,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Successful ShowAOAPacketHandler - 200", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentRequestBulkDownloadHandler{ + handler := ShowPaymentPacketHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } @@ -728,7 +728,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Unsuccessful ShowPaymentPacketHandler - InternalServerError", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentRequestBulkDownloadHandler{ + handler := ShowPaymentPacketHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } @@ -754,7 +754,7 @@ func (suite *HandlerSuite) TestShowPaymentPacketHandler() { suite.Run("Unsuccessful ShowPaymentPacketHandler - NotFoundError", func() { mockPaymentPacketCreator := mocks.PaymentPacketCreator{} - handler := ShowPaymentRequestBulkDownloadHandler{ + handler := ShowPaymentPacketHandler{ HandlerConfig: suite.createS3HandlerConfig(), PaymentPacketCreator: &mockPaymentPacketCreator, } From da757989ecf02a1dc915e4484d7674b62bf7fedb Mon Sep 17 00:00:00 2001 From: loganwc Date: Thu, 5 Sep 2024 18:32:28 +0000 Subject: [PATCH 06/10] removed duplicate test stuff --- .../Office/PaymentRequestReview/PaymentRequestReview.test.jsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx index 89727aacda2..1426bb27a60 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx @@ -526,10 +526,6 @@ describe('PaymentRequestReview', () => { await userEvent.click(screen.getByRole('button', { name: 'Previous Service Item' })); expect(screen.getByTestId('statusHeading')).toHaveTextContent('Accepted'); }); - it('shows the Download All Files (PDF) button and downloads the pdf', async () => { - expect(screen.getByText('Download All Files (PDF)')).toBeInTheDocument(); - await userEvent.click(screen.getByText('Download All Files (PDF)')); - }); }); }); }); From da09f5e2222d084ec6dc57a3b0033e578717b79b Mon Sep 17 00:00:00 2001 From: loganwc Date: Mon, 9 Sep 2024 16:09:18 +0000 Subject: [PATCH 07/10] moved download button into document viewer component --- .../DocumentViewer/DocumentViewer.jsx | 20 +++++++++++++++-- .../DocumentViewer/DocumentViewer.module.scss | 7 ++++++ .../PaymentRequestReview.jsx | 22 ++----------------- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/components/DocumentViewer/DocumentViewer.jsx b/src/components/DocumentViewer/DocumentViewer.jsx index 98494e3f163..73daff482d3 100644 --- a/src/components/DocumentViewer/DocumentViewer.jsx +++ b/src/components/DocumentViewer/DocumentViewer.jsx @@ -12,9 +12,10 @@ import Menu from './Menu/Menu'; import { milmoveLogger } from 'utils/milmoveLog'; import { UPLOADS } from 'constants/queryKeys'; -import { updateUpload } from 'services/ghcApi'; +import { bulkDownloadPaymentRequest, updateUpload } from 'services/ghcApi'; import { formatDate } from 'shared/dates'; import { filenameFromPath } from 'utils/formatters'; +import AsyncPacketDownloadLink from 'shared/AsyncPacketDownloadLink/AsyncPacketDownloadLink'; /** * TODO @@ -23,7 +24,7 @@ import { filenameFromPath } from 'utils/formatters'; * - handle fetch doc errors */ -const DocumentViewer = ({ files, allowDownload }) => { +const DocumentViewer = ({ files, allowDownload, paymentRequestId }) => { const [selectedFileIndex, selectFile] = useState(0); const [disableSaveButton, setDisableSaveButton] = useState(false); const [menuIsOpen, setMenuOpen] = useState(false); @@ -117,6 +118,20 @@ const DocumentViewer = ({ files, allowDownload }) => { } }; + const paymentPacketDownload = ( +

+
+

+ +

+
+
+ ); + return (
@@ -133,6 +148,7 @@ const DocumentViewer = ({ files, allowDownload }) => {

)} + {paymentRequestId !== undefined ? paymentPacketDownload : null}
{ const navigate = useNavigate(); @@ -173,28 +172,11 @@ export const PaymentRequestReview = ({ order }) => { navigate(`/moves/${moveCode}/payment-requests`); }; - const paymentPacketDownload = ( -
-
-

- -

-
-
- ); - return (
{uploads.length > 0 ? ( - <> - {paymentPacketDownload} - - + ) : (

No documents provided

)} From f4b2fe63288f3f8129fd707625ad8f7ab2a1dfa0 Mon Sep 17 00:00:00 2001 From: loganwc Date: Mon, 9 Sep 2024 16:33:45 +0000 Subject: [PATCH 08/10] moved bulk download test --- .../DocumentViewer/DocumentViewer.test.jsx | 42 ++++++++++++++++++- .../PaymentRequestReview.test.jsx | 36 ++-------------- 2 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/components/DocumentViewer/DocumentViewer.test.jsx b/src/components/DocumentViewer/DocumentViewer.test.jsx index 31d705ea4f5..b8306cbdf58 100644 --- a/src/components/DocumentViewer/DocumentViewer.test.jsx +++ b/src/components/DocumentViewer/DocumentViewer.test.jsx @@ -1,6 +1,6 @@ /* eslint-disable react/jsx-props-no-spreading */ import React from 'react'; -import { render, screen } from '@testing-library/react'; +import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { QueryClientProvider, QueryClient } from '@tanstack/react-query'; @@ -10,6 +10,8 @@ import sampleJPG from './sample.jpg'; import samplePNG from './sample2.png'; import sampleGIF from './sample3.gif'; +import { bulkDownloadPaymentRequest } from 'services/ghcApi'; + const toggleMenuClass = () => { const container = document.querySelector('[data-testid="menuButtonContainer"]'); if (container) { @@ -50,6 +52,11 @@ const mockFiles = [ }, ]; +jest.mock('services/ghcApi', () => ({ + ...jest.requireActual('services/ghcApi'), + bulkDownloadPaymentRequest: jest.fn(), +})); + jest.mock('./Content/Content', () => ({ __esModule: true, default: ({ id, filename, contentType, url, createdAt, rotation }) => ( @@ -172,4 +179,37 @@ describe('DocumentViewer component', () => { expect(screen.getByText('id: undefined')).toBeInTheDocument(); }); + + describe('when clicking download Download All Files button', () => { + it('downloads a bulk packet', async () => { + const mockResponse = { + ok: true, + headers: { + 'content-disposition': 'filename="test.pdf"', + }, + status: 200, + data: null, + }; + + render( + + + , + ); + + bulkDownloadPaymentRequest.mockImplementation(() => Promise.resolve(mockResponse)); + + const downloadButton = screen.getByText('Download All Files (PDF)', { exact: false }); + await userEvent.click(downloadButton); + await waitFor(() => { + expect(bulkDownloadPaymentRequest).toHaveBeenCalledTimes(1); + }); + }); + }); }); diff --git a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx index 1426bb27a60..f95bd113559 100644 --- a/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx +++ b/src/pages/Office/PaymentRequestReview/PaymentRequestReview.test.jsx @@ -6,7 +6,7 @@ import userEvent from '@testing-library/user-event'; import { PaymentRequestReview } from './PaymentRequestReview'; -import { patchPaymentServiceItemStatus, bulkDownloadPaymentRequest } from 'services/ghcApi'; +import { patchPaymentServiceItemStatus } from 'services/ghcApi'; import { SHIPMENT_OPTIONS, PAYMENT_REQUEST_STATUS, PAYMENT_SERVICE_ITEM_STATUS } from 'shared/constants'; import { usePaymentRequestQueries } from 'hooks/queries'; import { ReactQueryWrapper } from 'testUtils'; @@ -57,7 +57,6 @@ jest.mock('hooks/queries', () => ({ jest.mock('services/ghcApi', () => ({ ...jest.requireActual('services/ghcApi'), patchPaymentServiceItemStatus: jest.fn(), - bulkDownloadPaymentRequest: jest.fn(), })); // prevents react-fileviewer from throwing errors without mocking relevant DOM elements @@ -354,34 +353,7 @@ describe('PaymentRequestReview', () => { expect(reviewServiceItems.prop('serviceItemCards')).toEqual(expectedServiceItemCards); }); }); - describe('when clicking download Download All Files button', () => { - it('downloads a bulk packet', async () => { - usePaymentRequestQueries.mockReturnValue(usePaymentRequestQueriesReturnValuePendingFinalReview); - const mockResponse = { - ok: true, - headers: { - 'content-disposition': 'filename="test.pdf"', - }, - status: 200, - data: null, - }; - - render( - - - , - ); - - bulkDownloadPaymentRequest.mockImplementation(() => Promise.resolve(mockResponse)); - - const downloadButton = screen.getByText('Download All Files (PDF)', { exact: false }); - await userEvent.click(downloadButton); - await waitFor(() => { - expect(bulkDownloadPaymentRequest).toHaveBeenCalledTimes(1); - }); - }); - }); describe('clicking the next button', () => { describe('with pending requests', () => { beforeEach(async () => { @@ -512,9 +484,9 @@ describe('PaymentRequestReview', () => { expect(terms[1]).toHaveTextContent('Accepted'); expect(terms[2]).toHaveTextContent('Rejected'); const definitions = screen.getAllByRole('definition'); - expect(definitions[1]).toHaveTextContent('$1,703.10'); - expect(definitions[2]).toHaveTextContent('$1,579.98'); - expect(definitions[3]).toHaveTextContent('$123.12'); + expect(definitions[0]).toHaveTextContent('$1,703.10'); + expect(definitions[1]).toHaveTextContent('$1,579.98'); + expect(definitions[2]).toHaveTextContent('$123.12'); }); it('navigates back, and shows the correct icons for approved and rejected cards', async () => { await userEvent.click(screen.getByRole('button', { name: 'Back' })); From 6ca3ba8d5effe34015a16ab51e24f1e707f58426 Mon Sep 17 00:00:00 2001 From: loganwc Date: Tue, 10 Sep 2024 14:38:40 +0000 Subject: [PATCH 09/10] remove unused responses --- pkg/gen/ghcapi/embedded_spec.go | 18 ---- .../bulk_download_responses.go | 90 ------------------- swagger-def/ghc.yaml | 4 - swagger/ghc.yaml | 4 - 4 files changed, 116 deletions(-) diff --git a/pkg/gen/ghcapi/embedded_spec.go b/pkg/gen/ghcapi/embedded_spec.go index fb3aedf106b..417a21286f3 100644 --- a/pkg/gen/ghcapi/embedded_spec.go +++ b/pkg/gen/ghcapi/embedded_spec.go @@ -3202,12 +3202,6 @@ func init() { "400": { "$ref": "#/responses/InvalidRequest" }, - "403": { - "$ref": "#/responses/PermissionDenied" - }, - "404": { - "$ref": "#/responses/NotFound" - }, "500": { "$ref": "#/responses/ServerError" } @@ -17827,18 +17821,6 @@ func init() { "$ref": "#/definitions/Error" } }, - "403": { - "description": "The request was denied", - "schema": { - "$ref": "#/definitions/Error" - } - }, - "404": { - "description": "The requested resource wasn't found", - "schema": { - "$ref": "#/definitions/Error" - } - }, "500": { "description": "A server error occurred", "schema": { diff --git a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go index aa2aa194d11..b00eca8e5c1 100644 --- a/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go +++ b/pkg/gen/ghcapi/ghcoperations/payment_requests/bulk_download_responses.go @@ -124,96 +124,6 @@ func (o *BulkDownloadBadRequest) WriteResponse(rw http.ResponseWriter, producer } } -// BulkDownloadForbiddenCode is the HTTP code returned for type BulkDownloadForbidden -const BulkDownloadForbiddenCode int = 403 - -/* -BulkDownloadForbidden The request was denied - -swagger:response bulkDownloadForbidden -*/ -type BulkDownloadForbidden struct { - - /* - In: Body - */ - Payload *ghcmessages.Error `json:"body,omitempty"` -} - -// NewBulkDownloadForbidden creates BulkDownloadForbidden with default headers values -func NewBulkDownloadForbidden() *BulkDownloadForbidden { - - return &BulkDownloadForbidden{} -} - -// WithPayload adds the payload to the bulk download forbidden response -func (o *BulkDownloadForbidden) WithPayload(payload *ghcmessages.Error) *BulkDownloadForbidden { - o.Payload = payload - return o -} - -// SetPayload sets the payload to the bulk download forbidden response -func (o *BulkDownloadForbidden) SetPayload(payload *ghcmessages.Error) { - o.Payload = payload -} - -// WriteResponse to the client -func (o *BulkDownloadForbidden) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.WriteHeader(403) - if o.Payload != nil { - payload := o.Payload - if err := producer.Produce(rw, payload); err != nil { - panic(err) // let the recovery middleware deal with this - } - } -} - -// BulkDownloadNotFoundCode is the HTTP code returned for type BulkDownloadNotFound -const BulkDownloadNotFoundCode int = 404 - -/* -BulkDownloadNotFound The requested resource wasn't found - -swagger:response bulkDownloadNotFound -*/ -type BulkDownloadNotFound struct { - - /* - In: Body - */ - Payload *ghcmessages.Error `json:"body,omitempty"` -} - -// NewBulkDownloadNotFound creates BulkDownloadNotFound with default headers values -func NewBulkDownloadNotFound() *BulkDownloadNotFound { - - return &BulkDownloadNotFound{} -} - -// WithPayload adds the payload to the bulk download not found response -func (o *BulkDownloadNotFound) WithPayload(payload *ghcmessages.Error) *BulkDownloadNotFound { - o.Payload = payload - return o -} - -// SetPayload sets the payload to the bulk download not found response -func (o *BulkDownloadNotFound) SetPayload(payload *ghcmessages.Error) { - o.Payload = payload -} - -// WriteResponse to the client -func (o *BulkDownloadNotFound) WriteResponse(rw http.ResponseWriter, producer runtime.Producer) { - - rw.WriteHeader(404) - if o.Payload != nil { - payload := o.Payload - if err := producer.Produce(rw, payload); err != nil { - panic(err) // let the recovery middleware deal with this - } - } -} - // BulkDownloadInternalServerErrorCode is the HTTP code returned for type BulkDownloadInternalServerError const BulkDownloadInternalServerErrorCode int = 500 diff --git a/swagger-def/ghc.yaml b/swagger-def/ghc.yaml index 9c6b01ad2c3..5549e7ec03f 100644 --- a/swagger-def/ghc.yaml +++ b/swagger-def/ghc.yaml @@ -3133,10 +3133,6 @@ paths: type: file '400': $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' '500': $ref: '#/responses/ServerError' /documents/{documentId}: diff --git a/swagger/ghc.yaml b/swagger/ghc.yaml index 81d1d5571b1..8f1f6cfc876 100644 --- a/swagger/ghc.yaml +++ b/swagger/ghc.yaml @@ -3247,10 +3247,6 @@ paths: type: file '400': $ref: '#/responses/InvalidRequest' - '403': - $ref: '#/responses/PermissionDenied' - '404': - $ref: '#/responses/NotFound' '500': $ref: '#/responses/ServerError' /documents/{documentId}: From c046eb1b0461f00cbbc9c513f372465d74f0548e Mon Sep 17 00:00:00 2001 From: loganwc Date: Fri, 20 Sep 2024 14:05:28 +0000 Subject: [PATCH 10/10] moved code 4 lines down --- pkg/handlers/ghcapi/api.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pkg/handlers/ghcapi/api.go b/pkg/handlers/ghcapi/api.go index 81db579c4e8..eb3bafeceb6 100644 --- a/pkg/handlers/ghcapi/api.go +++ b/pkg/handlers/ghcapi/api.go @@ -666,15 +666,15 @@ func NewGhcAPIHandler(handlerConfig handlers.HandlerConfig) *ghcops.MymoveAPI { order.NewOrderUpdater(moveRouter), } + ghcAPI.MoveMoveCancelerHandler = MoveCancelerHandler{ + handlerConfig, + move.NewMoveCanceler(), + } + paymentRequestBulkDownloadCreator := paymentrequest.NewPaymentRequestBulkDownloadCreator(pdfGenerator) ghcAPI.PaymentRequestsBulkDownloadHandler = PaymentRequestBulkDownloadHandler{ handlerConfig, paymentRequestBulkDownloadCreator, - } - - ghcAPI.MoveMoveCancelerHandler = MoveCancelerHandler{ - handlerConfig, - move.NewMoveCanceler(), } dateSelectionChecker := dateservice.NewDateSelectionChecker()