Skip to content

Commit

Permalink
Merge pull request #2018 from mk-165003104-disable-ssw-button-when-mi…
Browse files Browse the repository at this point in the history
…ssing-data

* Disable shipment summary worksheet button when required data is missing
* [Delivers #165003104]
  • Loading branch information
mkrump committed Apr 23, 2019
1 parent 46a7a43 commit c5d75f4
Show file tree
Hide file tree
Showing 11 changed files with 363 additions and 7 deletions.
1 change: 1 addition & 0 deletions pkg/handlers/internalapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func NewInternalAPIHandler(context handlers.HandlerContext) http.Handler {
internalAPI.UsersShowLoggedInUserHandler = ShowLoggedInUserHandler{context}

internalAPI.CertificationCreateSignedCertificationHandler = CreateSignedCertificationHandler{context}
internalAPI.CertificationIndexSignedCertificationHandler = IndexSignedCertificationsHandler{context}

internalAPI.PpmCreatePersonallyProcuredMoveHandler = CreatePersonallyProcuredMoveHandler{context}
internalAPI.PpmIndexPersonallyProcuredMovesHandler = IndexPersonallyProcuredMovesHandler{context}
Expand Down
24 changes: 23 additions & 1 deletion pkg/handlers/internalapi/signed_certifications.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func payloadForSignedCertificationModel(cert models.SignedCertification) *intern
CreatedAt: handlers.FmtDateTime(cert.CreatedAt),
Date: handlers.FmtDateTime(cert.Date),
ID: handlers.FmtUUID(cert.ID),
MoveID: handlers.FmtUUID(cert.MoveID),
PersonallyProcuredMoveID: handlers.FmtUUIDPtr(cert.PersonallyProcuredMoveID),
ShipmentID: handlers.FmtUUIDPtr(cert.ShipmentID),
Signature: handlers.FmtString(cert.Signature),
Expand Down Expand Up @@ -93,7 +94,28 @@ func (h CreateSignedCertificationHandler) Handle(params certop.CreateSignedCerti
return certop.NewCreateSignedCertificationCreated().WithPayload(signedCertificationPayload)
}

// IndexSignedCertificationsHandler creates a new issue via POST /issue
// IndexSignedCertificationsHandler gets all signed certifications associated with a move
type IndexSignedCertificationsHandler struct {
handlers.HandlerContext
}

// Handle gets a list of SignedCertifications for a move
func (h IndexSignedCertificationsHandler) Handle(params certop.IndexSignedCertificationParams) middleware.Responder {
session := auth.SessionFromRequestContext(params.HTTPRequest)
moveID, _ := uuid.FromString(params.MoveID.String())

_, err := models.FetchMove(h.DB(), session, moveID)
if err != nil {
return handlers.ResponseForError(h.Logger(), err)
}

signedCertifications, err := models.FetchSignedCertifications(h.DB(), session, moveID)
var signedCertificationsPayload internalmessages.SignedCertifications
for _, sc := range signedCertifications {
signedCertificationsPayload = append(signedCertificationsPayload, payloadForSignedCertificationModel(*sc))
}
if err != nil {
return handlers.ResponseForError(h.Logger(), err)
}
return certop.NewIndexSignedCertificationOK().WithPayload(signedCertificationsPayload)
}
105 changes: 105 additions & 0 deletions pkg/handlers/internalapi/signed_certifications_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,3 +129,108 @@ func (suite *HandlerSuite) TestCreateSignedCertificationHandlerBadMoveID() {
t.Errorf("Expected to find no signed certifications but found %v", len(certs))
}
}

func (suite *HandlerSuite) TestIndexSignedCertificationHandlerBadMoveID() {
ppm := testdatagen.MakeDefaultPPM(suite.DB())
move := ppm.Move
sm := ppm.Move.Orders.ServiceMember

ppmPayment := models.SignedCertificationTypePPMPAYMENT
testdatagen.MakeSignedCertification(suite.DB(), testdatagen.Assertions{
SignedCertification: models.SignedCertification{
MoveID: ppm.Move.ID,
SubmittingUserID: sm.User.ID,
PersonallyProcuredMoveID: &ppm.ID,
CertificationType: &ppmPayment,
CertificationText: "LEGAL",
Signature: "ACCEPT",
Date: testdatagen.NextValidMoveDate,
},
})

req := httptest.NewRequest("GET", "/move/id/thing", nil)
req = suite.AuthenticateRequest(req, move.Orders.ServiceMember)
badMoveID := strfmt.UUID("3511d4d6-019d-4031-9c27-8a553e055543")
params := certop.IndexSignedCertificationParams{
MoveID: badMoveID,
}

params.HTTPRequest = req

handler := IndexSignedCertificationsHandler{handlers.NewHandlerContext(suite.DB(), suite.TestLogger())}
response := handler.Handle(params)

suite.CheckResponseNotFound(response)
}

func (suite *HandlerSuite) TestIndexSignedCertificationHandlerMismatchedUser() {
ppm := testdatagen.MakeDefaultPPM(suite.DB())
move := ppm.Move
sm := ppm.Move.Orders.ServiceMember
ppmPayment := models.SignedCertificationTypePPMPAYMENT
testdatagen.MakeSignedCertification(suite.DB(), testdatagen.Assertions{
SignedCertification: models.SignedCertification{
MoveID: ppm.Move.ID,
SubmittingUserID: sm.User.ID,
PersonallyProcuredMoveID: &ppm.ID,
CertificationType: &ppmPayment,
CertificationText: "LEGAL",
Signature: "ACCEPT",
Date: testdatagen.NextValidMoveDate,
},
})
userUUID2, _ := uuid.FromString("3511d4d6-019d-4031-9c27-8a553e055543")
unauthorizedUser := models.User{
LoginGovUUID: userUUID2,
LoginGovEmail: "email2@example.com",
}
params := certop.IndexSignedCertificationParams{
MoveID: *handlers.FmtUUID(move.ID),
}
suite.MustSave(&unauthorizedUser)

req := httptest.NewRequest("GET", "/move/id/thing", nil)
req = suite.AuthenticateUserRequest(req, unauthorizedUser)

params.HTTPRequest = req

handler := IndexSignedCertificationsHandler{handlers.NewHandlerContext(suite.DB(), suite.TestLogger())}
response := handler.Handle(params)

suite.CheckResponseForbidden(response)
}

func (suite *HandlerSuite) TestIndexSignedCertificationHandler() {
ppm := testdatagen.MakeDefaultPPM(suite.DB())
move := ppm.Move
sm := ppm.Move.Orders.ServiceMember
ppmPayment := models.SignedCertificationTypePPMPAYMENT
testdatagen.MakeSignedCertification(suite.DB(), testdatagen.Assertions{
SignedCertification: models.SignedCertification{
MoveID: ppm.Move.ID,
SubmittingUserID: sm.User.ID,
PersonallyProcuredMoveID: &ppm.ID,
CertificationType: &ppmPayment,
CertificationText: "LEGAL",
Signature: "ACCEPT",
Date: testdatagen.NextValidMoveDate,
},
})
params := certop.IndexSignedCertificationParams{
MoveID: *handlers.FmtUUID(move.ID),
}

req := httptest.NewRequest("GET", "/move/id/thing", nil)
req = suite.AuthenticateRequest(req, sm)

params.HTTPRequest = req

handler := IndexSignedCertificationsHandler{handlers.NewHandlerContext(suite.DB(), suite.TestLogger())}
response := handler.Handle(params)

okResponse, ok := response.(*certop.IndexSignedCertificationOK)
suite.True(ok)
suite.Equal(1, len(okResponse.Payload))
responsePayload := okResponse.Payload[0]
suite.Equal(move.ID.String(), responsePayload.MoveID.String())
}
22 changes: 22 additions & 0 deletions pkg/models/signed_certification.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,25 @@ func FetchSignedCertificationsPPMPayment(db *pop.Connection, session *auth.Sessi

return &signedCertification, nil
}

// FetchSignedCertifications Fetches and Validates a all signed certifications associated with a move
func FetchSignedCertifications(db *pop.Connection, session *auth.Session, id uuid.UUID) ([]*SignedCertification, error) {
var signedCertification []*SignedCertification
err := db.Where("move_id = $1", id.String()).All(&signedCertification)

if err != nil {
if errors.Cause(err).Error() == recordNotFoundErrorString {
msg := fmt.Sprintf("signed_certification: with move_id: %s not found", id.String())
return nil, errors.Wrap(ErrFetchNotFound, msg)
}
// Otherwise, it's an unexpected err so we return that.
return nil, errors.Wrap(err, "signed_certification: unable to fetch signed certification")
}
// Validate the move is associated to the logged-in service member
_, fetchErr := FetchMove(db, session, id)
if fetchErr != nil {
return nil, errors.Wrap(ErrFetchForbidden, "signed_certification: unauthorized access")
}

return signedCertification, nil
}
60 changes: 60 additions & 0 deletions pkg/models/signed_certification_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package models_test

import (
"github.com/gofrs/uuid"
"github.com/pkg/errors"

"github.com/transcom/mymove/pkg/auth"
Expand Down Expand Up @@ -89,3 +90,62 @@ func (suite *ModelSuite) TestFetchSignedCertificationsPPMPaymentAuth() {
_, err := FetchSignedCertificationsPPMPayment(suite.DB(), session, otherPpm.MoveID)
suite.Equal(errors.Cause(err), ErrFetchForbidden)
}

func (suite *ModelSuite) TestFetchSignedCertifications() {
ppm := testdatagen.MakeDefaultPPM(suite.DB())
move := ppm.Move
sm := ppm.Move.Orders.ServiceMember

session := &auth.Session{
UserID: sm.UserID,
ServiceMemberID: sm.ID,
ApplicationName: auth.MilApp,
}

ppmPayment := SignedCertificationTypePPMPAYMENT
ppmPaymentsignedCertification := testdatagen.MakeSignedCertification(suite.DB(), testdatagen.Assertions{
SignedCertification: SignedCertification{
MoveID: ppm.Move.ID,
SubmittingUserID: sm.User.ID,
PersonallyProcuredMoveID: &ppm.ID,
CertificationType: &ppmPayment,
CertificationText: "LEGAL",
Signature: "ACCEPT",
Date: testdatagen.NextValidMoveDate,
},
})
ppmCert := SignedCertificationTypePPM
ppmSignedCertification := testdatagen.MakeSignedCertification(suite.DB(), testdatagen.Assertions{
SignedCertification: SignedCertification{
MoveID: ppm.Move.ID,
SubmittingUserID: sm.User.ID,
PersonallyProcuredMoveID: &ppm.ID,
CertificationType: &ppmCert,
CertificationText: "LEGAL",
Signature: "ACCEPT",
Date: testdatagen.NextValidMoveDate,
},
})
hhgCert := SignedCertificationTypeHHG
hhgSignedCertification := testdatagen.MakeSignedCertification(suite.DB(), testdatagen.Assertions{
SignedCertification: SignedCertification{
MoveID: ppm.Move.ID,
SubmittingUserID: sm.User.ID,
PersonallyProcuredMoveID: &ppm.ID,
CertificationType: &hhgCert,
CertificationText: "LEGAL",
Signature: "ACCEPT",
Date: testdatagen.NextValidMoveDate,
},
})

scs, err := FetchSignedCertifications(suite.DB(), session, move.ID)
var ids []uuid.UUID
for _, sc := range scs {
ids = append(ids, sc.ID)
}

suite.Len(scs, 3)
suite.Nil(err)
suite.ElementsMatch(ids, []uuid.UUID{hhgSignedCertification.ID, ppmSignedCertification.ID, ppmPaymentsignedCertification.ID})
}
40 changes: 37 additions & 3 deletions src/scenes/Office/Ppm/PaymentsPanel.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { get, isEmpty } from 'lodash';
import { get, isEmpty, includes } from 'lodash';
import React, { Component, Fragment } from 'react';
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
Expand All @@ -24,13 +24,33 @@ import faPlusSquare from '@fortawesome/fontawesome-free-solid/faPlusSquare';
import faMinusSquare from '@fortawesome/fontawesome-free-solid/faMinusSquare';

import './PaymentsPanel.css';
import { getSignedCertification } from 'shared/Entities/modules/signed_certifications';
import { selectPaymentRequestCertificationForMove } from 'shared/Entities/modules/signed_certifications';
import { selectShipmentForMove } from 'shared/Entities/modules/shipments';

const attachmentsErrorMessages = {
422: 'Encountered an error while trying to create attachments bundle: Document is in the wrong format',
424: 'Could not find any receipts or documents for this PPM',
500: 'An unexpected error has occurred',
};

export function sswIsDisabled(ppm, signedCertification, shipment) {
return (
missingSignature(signedCertification) || missingNetWeightOrActualMoveDate(ppm) || isComboAndNotDelivered(shipment)
);
}

function missingSignature(signedCertification) {
return isEmpty(signedCertification) || signedCertification.certification_type !== 'PPM_PAYMENT';
}

function missingNetWeightOrActualMoveDate(ppm) {
return isEmpty(ppm) || !ppm.net_weight || !ppm.actual_move_date;
}
function isComboAndNotDelivered(shipment) {
return !isEmpty(shipment) && !includes(['DELIVERED', 'COMPLETED'], shipment.status);
}

function getUserDate() {
return new Date().toISOString().split('T')[0];
}
Expand All @@ -41,6 +61,13 @@ class PaymentsTable extends Component {
disableDownload: false,
};

componentDidMount() {
const { moveId } = this.props;
if (moveId != null) {
this.props.getSignedCertification(moveId);
}
}

approveReimbursement = () => {
this.props.approveReimbursement(this.props.advance.id);
};
Expand Down Expand Up @@ -135,7 +162,7 @@ class PaymentsTable extends Component {
</th>
</tr>
<tr>
<td className="payment-table-column-content">Advance </td>
<td className="payment-table-column-content">Advance</td>
<td className="payment-table-column-content">
${formatCents(get(advance, 'requested_amount')).toLocaleString()}
</td>
Expand Down Expand Up @@ -190,7 +217,9 @@ class PaymentsTable extends Component {
<p>Download Shipment Summary Worksheet</p>
<p>Download and complete the worksheet, which is a fill-in PDF form.</p>
</div>
<button onClick={this.downloadShipmentSummary}>Download Worksheet (PDF)</button>
<button disabled={this.props.disableSSW} onClick={this.downloadShipmentSummary}>
Download Worksheet (PDF)
</button>
</div>

<hr />
Expand Down Expand Up @@ -250,9 +279,13 @@ class PaymentsTable extends Component {
const mapStateToProps = (state, ownProps) => {
const { moveId } = ownProps;
const ppm = selectPPMForMove(state, moveId);
const shipment = selectShipmentForMove(state, moveId);
const advance = selectReimbursement(state, ppm.advance);
const signedCertifications = selectPaymentRequestCertificationForMove(state, moveId);
const disableSSW = sswIsDisabled(ppm, signedCertifications, shipment);
return {
ppm,
disableSSW,
moveId,
advance,
attachmentsError: getLastError(state, downloadPPMAttachmentsLabel),
Expand All @@ -262,6 +295,7 @@ const mapStateToProps = (state, ownProps) => {
const mapDispatchToProps = dispatch =>
bindActionCreators(
{
getSignedCertification,
approveReimbursement,
update: no_op,
downloadPPMAttachments,
Expand Down
Loading

0 comments on commit c5d75f4

Please sign in to comment.