From ac24c6df78d12880657e2ced356e1adfb14747f4 Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Fri, 1 Nov 2024 16:08:40 +0000 Subject: [PATCH 01/13] B-21458 PPM move_payment_reminder email destination, for Retirement and Separation, fetch and use the destination address not the duty station. --- pkg/notifications/move_payment_reminder.go | 76 +++++++++-- .../move_payment_reminder_test.go | 125 ++++++++++++++++++ 2 files changed, 191 insertions(+), 10 deletions(-) diff --git a/pkg/notifications/move_payment_reminder.go b/pkg/notifications/move_payment_reminder.go index 1c3f5d7712b..d2987043724 100644 --- a/pkg/notifications/move_payment_reminder.go +++ b/pkg/notifications/move_payment_reminder.go @@ -11,6 +11,7 @@ import ( "github.com/transcom/mymove/pkg/appcontext" "github.com/transcom/mymove/pkg/assets" + "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" "github.com/transcom/mymove/pkg/unit" ) @@ -46,14 +47,21 @@ type PaymentReminderEmailInfos []PaymentReminderEmailInfo // PaymentReminderEmailInfo contains payment reminder data for rendering a template type PaymentReminderEmailInfo struct { - ServiceMemberID uuid.UUID `db:"id"` - Email *string `db:"personal_email"` - NewDutyLocationName string `db:"new_duty_location_name"` - OriginDutyLocationName string `db:"origin_duty_location_name"` - MoveDate string `db:"move_date"` - Locator string `db:"locator"` - WeightEstimate *unit.Pound `db:"weight_estimate"` - IncentiveEstimate *unit.Cents `db:"incentive_estimate"` + ServiceMemberID uuid.UUID `db:"id"` + Email *string `db:"personal_email"` + NewDutyLocationName string `db:"new_duty_location_name"` + OriginDutyLocationName string `db:"origin_duty_location_name"` + MoveDate string `db:"move_date"` + Locator string `db:"locator"` + WeightEstimate *unit.Pound `db:"weight_estimate"` + IncentiveEstimate *unit.Cents `db:"incentive_estimate"` + DestinationStreet1 *string `db:"destination_street_address_1"` + DestinationStreet2 *string `db:"destination_street_address_2"` + DestinationStreet3 *string `db:"destination_street_address_3"` + DestinationCity *string `db:"destination_city"` + DestinationState *string `db:"destination_state"` + DestinationPostalCode *string `db:"destination_postal_code"` + OrdersType internalmessages.OrdersType `db:"orders_type"` } // GetEmailInfo fetches payment email information @@ -65,7 +73,14 @@ func (m PaymentReminder) GetEmailInfo(appCtx appcontext.AppContext) (PaymentRemi ps.expected_departure_date as move_date, dln.name AS new_duty_location_name, dln2.name AS origin_duty_location_name, - m.locator + m.locator, + da.street_address_1 AS destination_street_address_1, + da.street_address_2 AS destination_street_address_2, + da.street_address_3 AS destination_street_address_3, + da.city AS destination_city, + da.state AS destination_state, + da.postal_code AS destination_postal_code, + o.orders_type FROM ppm_shipments ps JOIN mto_shipments ms on ms.id = ps.shipment_id JOIN moves m ON ms.move_id = m.id @@ -73,6 +88,7 @@ FROM ppm_shipments ps JOIN service_members sm ON o.service_member_id = sm.id JOIN duty_locations dln ON o.new_duty_location_id = dln.id JOIN duty_locations dln2 ON o.origin_duty_location_id = dln2.id + JOIN addresses da ON ps.destination_postal_address_id = da.id WHERE ps.status = 'WAITING_ON_CUSTOMER'::public."ppm_shipment_status" AND ms.status = 'APPROVED'::public."mto_shipment_status" AND ps.expected_departure_date <= now() - ($1)::interval @@ -104,13 +120,14 @@ func (m PaymentReminder) emails(appCtx appcontext.AppContext) ([]emailContent, e return m.formatEmails(appCtx, paymentReminderEmailInfos) } +// TODO: rename to DestinationLocation // formatEmails formats email data using both html and text template func (m PaymentReminder) formatEmails(appCtx appcontext.AppContext, PaymentReminderEmailInfos PaymentReminderEmailInfos) ([]emailContent, error) { var emails []emailContent for _, PaymentReminderEmailInfo := range PaymentReminderEmailInfos { htmlBody, textBody, err := m.renderTemplates(appCtx, PaymentReminderEmailData{ OriginDutyLocation: PaymentReminderEmailInfo.OriginDutyLocationName, - DestinationDutyLocation: PaymentReminderEmailInfo.NewDutyLocationName, + DestinationDutyLocation: getDestinationLocation(appCtx, PaymentReminderEmailInfo), Locator: PaymentReminderEmailInfo.Locator, OneSourceLink: OneSourceTransportationOfficeLink, MyMoveLink: MyMoveLink, @@ -152,6 +169,45 @@ func (m PaymentReminder) renderTemplates(appCtx appcontext.AppContext, data Paym return htmlBody, textBody, nil } +func getDestinationLocation(appCtx appcontext.AppContext, PaymentReminderEmailInfo PaymentReminderEmailInfo) string { + destinationLocation := PaymentReminderEmailInfo.NewDutyLocationName + ordersType := PaymentReminderEmailInfo.OrdersType + street1 := PaymentReminderEmailInfo.DestinationStreet1 + if street1 != nil { + appCtx.Logger().Error("Street1 is: " + *street1) + } else { + appCtx.Logger().Error("Street1 is nil") + } + isSeparateeOrRetireeOrder := ordersType == internalmessages.OrdersTypeRETIREMENT || ordersType == internalmessages.OrdersTypeSEPARATION + if isSeparateeOrRetireeOrder { + appCtx.Logger().Debug("isSeparateeOrRetireeOrder: true") + } else { + appCtx.Logger().Debug("isSeparateeOrRetireeOrder: false") + } + if isSeparateeOrRetireeOrder && street1 != nil { + appCtx.Logger().Debug("In address section") + street2, street3, city, state, postalCode := "", "", "", "", "" + if PaymentReminderEmailInfo.DestinationStreet2 != nil { + street2 = " " + *PaymentReminderEmailInfo.DestinationStreet2 + } + if PaymentReminderEmailInfo.DestinationStreet3 != nil { + street3 = " " + *PaymentReminderEmailInfo.DestinationStreet3 + } + if PaymentReminderEmailInfo.DestinationCity != nil { + city = ", " + *PaymentReminderEmailInfo.DestinationCity + } + if PaymentReminderEmailInfo.DestinationState != nil { + state = ", " + *PaymentReminderEmailInfo.DestinationState + } + if PaymentReminderEmailInfo.DestinationPostalCode != nil { + postalCode = " " + *PaymentReminderEmailInfo.DestinationPostalCode + } + destinationLocation = fmt.Sprintf("%s%s%s%s%s%s", *street1, street2, street3, city, state, postalCode) + appCtx.Logger().Debug("New location: " + destinationLocation) + } + return destinationLocation +} + // OnSuccess callback passed to be invoked by NewNotificationSender when an email successfully sent // saves the svs the email info along with the SES mail id to the notifications table func (m PaymentReminder) OnSuccess(appCtx appcontext.AppContext, PaymentReminderEmailInfo PaymentReminderEmailInfo) func(string) error { diff --git a/pkg/notifications/move_payment_reminder_test.go b/pkg/notifications/move_payment_reminder_test.go index 2da424994f3..f76107d8b21 100644 --- a/pkg/notifications/move_payment_reminder_test.go +++ b/pkg/notifications/move_payment_reminder_test.go @@ -4,6 +4,7 @@ import ( "time" "github.com/transcom/mymove/pkg/factory" + "github.com/transcom/mymove/pkg/gen/internalmessages" "github.com/transcom/mymove/pkg/models" ) @@ -304,3 +305,127 @@ func (suite *NotificationSuite) TestFormatPaymentRequestedEmails() { // only expect the three moves with non-nil email addresses to get added to formattedEmails suite.Len(formattedEmails, 3) } + +func (suite *NotificationSuite) TestFormatPaymentRequestedEmailsForRetireeSeparation() { + pr, err := NewPaymentReminder() + suite.NoError(err) + + email1 := "email1" + streetOne1 := "100 Street Rd" + streetTwo1 := "STE 1" + streetThree1 := "Floor 1" + city1 := "Alpha City" + state1 := "Alabama" + postalCode1 := "11111" + expectedDestination1 := "100 Street Rd STE 1 Floor 1, Alpha City, Alabama 11111" + + email2 := "email2" + expectedDestination2 := "nd2" // no street address, fall back to duty station + + email3 := "email3" + streetOne3 := "300 Highway Ln" + city3 := "Charlie City" + state3 := "California" + postalCode3 := "33333" + expectedDestination3 := "300 Highway Ln, Charlie City, California 33333" + + email4 := "email4" + streetOne4 := "400 Unused Blvd" + city4 := "Delta City" + state4 := "Delaware" + postalCode4 := "44444" + expectedDestination4 := "nd4" // Permanent Change of Station, ignore street address + + email5 := "email5" + streetOne5 := "500 Parkway Dr" + expectedDestination5 := "500 Parkway Dr" // Tolerate other nil address fields + + expectedDestinations := []string{ + expectedDestination1, + expectedDestination2, + expectedDestination3, + expectedDestination4, + expectedDestination5, + } + + emailInfos := PaymentReminderEmailInfos{ + { + Email: &email1, + NewDutyLocationName: "nd1", + Locator: "abc123", + OrdersType: internalmessages.OrdersTypeRETIREMENT, + DestinationStreet1: &streetOne1, + DestinationStreet2: &streetTwo1, + DestinationStreet3: &streetThree1, + DestinationCity: &city1, + DestinationState: &state1, + DestinationPostalCode: &postalCode1, + }, + { + Email: &email2, + NewDutyLocationName: "nd2", + Locator: "abc456", + OrdersType: internalmessages.OrdersTypeRETIREMENT, + }, + { + Email: &email3, + NewDutyLocationName: "nd3", + Locator: "def123", + OrdersType: internalmessages.OrdersTypeSEPARATION, + DestinationStreet1: &streetOne3, + DestinationCity: &city3, + DestinationState: &state3, + DestinationPostalCode: &postalCode3, + }, + { + Email: &email4, + NewDutyLocationName: "nd4", + Locator: "def456", + OrdersType: internalmessages.OrdersTypePERMANENTCHANGEOFSTATION, + DestinationStreet1: &streetOne4, + DestinationCity: &city4, + DestinationState: &state4, + DestinationPostalCode: &postalCode4, + }, + { + Email: &email5, + NewDutyLocationName: "nd5", + Locator: "ghi123", + OrdersType: internalmessages.OrdersTypeRETIREMENT, + DestinationStreet1: &streetOne5, + DestinationCity: nil, + DestinationState: nil, + DestinationPostalCode: nil, + }, + } + formattedEmails, err := pr.formatEmails(suite.AppContextForTest(), emailInfos) + suite.NoError(err) + + for i, actualEmailContent := range formattedEmails { + emailInfo := emailInfos[i] + expectedDestination := expectedDestinations[i] + + data := PaymentReminderEmailData{ + DestinationDutyLocation: expectedDestination, + Locator: emailInfo.Locator, + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, + } + htmlBody, err := pr.RenderHTML(suite.AppContextForTest(), data) + suite.NoError(err) + textBody, err := pr.RenderText(suite.AppContextForTest(), data) + suite.NoError(err) + expectedEmailContent := emailContent{ + recipientEmail: *emailInfo.Email, + subject: "Complete your Personally Procured Move (PPM)", + htmlBody: htmlBody, + textBody: textBody, + } + if emailInfo.Email != nil { + suite.Equal(expectedEmailContent.recipientEmail, actualEmailContent.recipientEmail) + suite.Equal(expectedEmailContent.subject, actualEmailContent.subject) + suite.Equal(expectedEmailContent.textBody, actualEmailContent.textBody) + } + } + suite.Len(formattedEmails, 5) +} From 2f7e89fe94b3402da9df7795e64eb7f895b24374 Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Mon, 4 Nov 2024 14:54:09 +0000 Subject: [PATCH 02/13] B-21458 additional test for the query. --- pkg/notifications/move_payment_reminder_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/pkg/notifications/move_payment_reminder_test.go b/pkg/notifications/move_payment_reminder_test.go index f76107d8b21..5d43c7057ea 100644 --- a/pkg/notifications/move_payment_reminder_test.go +++ b/pkg/notifications/move_payment_reminder_test.go @@ -78,6 +78,13 @@ func (suite *NotificationSuite) TestPaymentReminderFetchSomeFound() { suite.Equal(ppms[i].EstimatedWeight, emailInfo[j].WeightEstimate) suite.Equal(ppms[i].EstimatedIncentive, emailInfo[j].IncentiveEstimate) suite.Equal(ppms[i].Shipment.MoveTaskOrder.Locator, emailInfo[j].Locator) + suite.Equal(ppms[i].DestinationAddress.StreetAddress1, *emailInfo[j].DestinationStreet1) + suite.Equal(ppms[i].DestinationAddress.StreetAddress2, emailInfo[j].DestinationStreet2) + suite.Equal(ppms[i].DestinationAddress.StreetAddress3, emailInfo[j].DestinationStreet3) + suite.Equal(ppms[i].DestinationAddress.City, *emailInfo[j].DestinationCity) + suite.Equal(ppms[i].DestinationAddress.State, *emailInfo[j].DestinationState) + suite.Equal(ppms[i].DestinationAddress.PostalCode, *emailInfo[j].DestinationPostalCode) + suite.Equal(ppms[i].Shipment.MoveTaskOrder.Orders.OrdersType, emailInfo[j].OrdersType) } } } From c4d647bd028c83beeba36665fb899871f82a376a Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:05:59 +0000 Subject: [PATCH 03/13] B-21458 rename destination field, not necessarily the duty station. --- .../move_payment_reminder_template.html | 2 +- .../move_payment_reminder_template.txt | 2 +- pkg/notifications/move_payment_reminder.go | 21 +++++----- .../move_payment_reminder_test.go | 40 +++++++++---------- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/pkg/assets/notifications/templates/move_payment_reminder_template.html b/pkg/assets/notifications/templates/move_payment_reminder_template.html index bf886147d56..d645b79eb98 100644 --- a/pkg/assets/notifications/templates/move_payment_reminder_template.html +++ b/pkg/assets/notifications/templates/move_payment_reminder_template.html @@ -1,7 +1,7 @@
*** DO NOT REPLY directly to this email ***
This is a reminder that your PPM with the assigned move code {{.Locator}} from -{{.OriginDutyLocation}} to {{.DestinationDutyLocation}} is awaiting action in MilMove.
+{{.OriginDutyLocation}} to {{.DestinationLocation}} is awaiting action in MilMove.Next steps:
diff --git a/pkg/assets/notifications/templates/move_payment_reminder_template.txt b/pkg/assets/notifications/templates/move_payment_reminder_template.txt index ccb29c418ab..f23d1b1deae 100644 --- a/pkg/assets/notifications/templates/move_payment_reminder_template.txt +++ b/pkg/assets/notifications/templates/move_payment_reminder_template.txt @@ -1,7 +1,7 @@ *** DO NOT REPLY directly to this email *** This is a reminder that your PPM with the assigned move code {{.Locator}} from {{.OriginDutyLocation}} -to {{.DestinationDutyLocation}} is awaiting action in MilMove. +to {{.DestinationLocation}} is awaiting action in MilMove. Next steps: diff --git a/pkg/notifications/move_payment_reminder.go b/pkg/notifications/move_payment_reminder.go index d2987043724..ca9d560b12c 100644 --- a/pkg/notifications/move_payment_reminder.go +++ b/pkg/notifications/move_payment_reminder.go @@ -120,17 +120,16 @@ func (m PaymentReminder) emails(appCtx appcontext.AppContext) ([]emailContent, e return m.formatEmails(appCtx, paymentReminderEmailInfos) } -// TODO: rename to DestinationLocation // formatEmails formats email data using both html and text template func (m PaymentReminder) formatEmails(appCtx appcontext.AppContext, PaymentReminderEmailInfos PaymentReminderEmailInfos) ([]emailContent, error) { var emails []emailContent for _, PaymentReminderEmailInfo := range PaymentReminderEmailInfos { htmlBody, textBody, err := m.renderTemplates(appCtx, PaymentReminderEmailData{ - OriginDutyLocation: PaymentReminderEmailInfo.OriginDutyLocationName, - DestinationDutyLocation: getDestinationLocation(appCtx, PaymentReminderEmailInfo), - Locator: PaymentReminderEmailInfo.Locator, - OneSourceLink: OneSourceTransportationOfficeLink, - MyMoveLink: MyMoveLink, + OriginDutyLocation: PaymentReminderEmailInfo.OriginDutyLocationName, + DestinationLocation: getDestinationLocation(appCtx, PaymentReminderEmailInfo), + Locator: PaymentReminderEmailInfo.Locator, + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, }) if err != nil { appCtx.Logger().Error("error rendering template", zap.Error(err)) @@ -230,11 +229,11 @@ func (m PaymentReminder) OnSuccess(appCtx appcontext.AppContext, PaymentReminder // PaymentReminderEmailData is used to render an email template type PaymentReminderEmailData struct { - OriginDutyLocation string - DestinationDutyLocation string - Locator string - OneSourceLink string - MyMoveLink string + OriginDutyLocation string + DestinationLocation string + Locator string + OneSourceLink string + MyMoveLink string } // RenderHTML renders the html for the email diff --git a/pkg/notifications/move_payment_reminder_test.go b/pkg/notifications/move_payment_reminder_test.go index 5d43c7057ea..f685767ce2b 100644 --- a/pkg/notifications/move_payment_reminder_test.go +++ b/pkg/notifications/move_payment_reminder_test.go @@ -144,16 +144,16 @@ func (suite *NotificationSuite) TestPaymentReminderHTMLTemplateRender() { suite.NoError(err) paymentReminderData := PaymentReminderEmailData{ - OriginDutyLocation: "OriginDutyLocation", - DestinationDutyLocation: "DestDutyLocation", - Locator: "abc123", - OneSourceLink: OneSourceTransportationOfficeLink, - MyMoveLink: MyMoveLink, + OriginDutyLocation: "OriginDutyLocation", + DestinationLocation: "DestDutyLocation", + Locator: "abc123", + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, } expectedHTMLContent := `*** DO NOT REPLY directly to this email ***
This is a reminder that your PPM with the assigned move code ` + paymentReminderData.Locator + ` from -` + paymentReminderData.OriginDutyLocation + ` to ` + paymentReminderData.DestinationDutyLocation + ` is awaiting action in MilMove.
+` + paymentReminderData.OriginDutyLocation + ` to ` + paymentReminderData.DestinationLocation + ` is awaiting action in MilMove.Next steps:
@@ -201,17 +201,17 @@ func (suite *NotificationSuite) TestPaymentReminderTextTemplateRender() { pr, err := NewPaymentReminder() suite.NoError(err) paymentReminderData := PaymentReminderEmailData{ - OriginDutyLocation: "OriginDutyLocation", - DestinationDutyLocation: "DestDutyLocation", - Locator: "abc123", - OneSourceLink: OneSourceTransportationOfficeLink, - MyMoveLink: MyMoveLink, + OriginDutyLocation: "OriginDutyLocation", + DestinationLocation: "DestDutyLocation", + Locator: "abc123", + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, } expectedTextContent := `*** DO NOT REPLY directly to this email *** This is a reminder that your PPM with the assigned move code ` + paymentReminderData.Locator + ` from ` + paymentReminderData.OriginDutyLocation + ` -to ` + paymentReminderData.DestinationDutyLocation + ` is awaiting action in MilMove. +to ` + paymentReminderData.DestinationLocation + ` is awaiting action in MilMove. Next steps: @@ -288,10 +288,10 @@ func (suite *NotificationSuite) TestFormatPaymentRequestedEmails() { emailInfo := emailInfos[i] data := PaymentReminderEmailData{ - DestinationDutyLocation: emailInfo.NewDutyLocationName, - Locator: emailInfo.Locator, - OneSourceLink: OneSourceTransportationOfficeLink, - MyMoveLink: MyMoveLink, + DestinationLocation: emailInfo.NewDutyLocationName, + Locator: emailInfo.Locator, + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, } htmlBody, err := pr.RenderHTML(suite.AppContextForTest(), data) suite.NoError(err) @@ -413,10 +413,10 @@ func (suite *NotificationSuite) TestFormatPaymentRequestedEmailsForRetireeSepara expectedDestination := expectedDestinations[i] data := PaymentReminderEmailData{ - DestinationDutyLocation: expectedDestination, - Locator: emailInfo.Locator, - OneSourceLink: OneSourceTransportationOfficeLink, - MyMoveLink: MyMoveLink, + DestinationLocation: expectedDestination, + Locator: emailInfo.Locator, + OneSourceLink: OneSourceTransportationOfficeLink, + MyMoveLink: MyMoveLink, } htmlBody, err := pr.RenderHTML(suite.AppContextForTest(), data) suite.NoError(err) From 0982954eb06b0ad08842922186492b535bfad37a Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Tue, 5 Nov 2024 16:09:02 +0000 Subject: [PATCH 04/13] B-21458 add htmlBody to the unit tests. --- pkg/notifications/move_payment_reminder_test.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/notifications/move_payment_reminder_test.go b/pkg/notifications/move_payment_reminder_test.go index f685767ce2b..2b30b22b139 100644 --- a/pkg/notifications/move_payment_reminder_test.go +++ b/pkg/notifications/move_payment_reminder_test.go @@ -307,6 +307,7 @@ func (suite *NotificationSuite) TestFormatPaymentRequestedEmails() { suite.Equal(expectedEmailContent.recipientEmail, actualEmailContent.recipientEmail) suite.Equal(expectedEmailContent.subject, actualEmailContent.subject) suite.Equal(expectedEmailContent.textBody, actualEmailContent.textBody) + suite.Equal(expectedEmailContent.htmlBody, actualEmailContent.htmlBody) } } // only expect the three moves with non-nil email addresses to get added to formattedEmails @@ -432,6 +433,7 @@ func (suite *NotificationSuite) TestFormatPaymentRequestedEmailsForRetireeSepara suite.Equal(expectedEmailContent.recipientEmail, actualEmailContent.recipientEmail) suite.Equal(expectedEmailContent.subject, actualEmailContent.subject) suite.Equal(expectedEmailContent.textBody, actualEmailContent.textBody) + suite.Equal(expectedEmailContent.htmlBody, actualEmailContent.htmlBody) } } suite.Len(formattedEmails, 5) From 129a44c682f17cf4b9a790890bb3007ccf1b18ee Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Wed, 6 Nov 2024 20:38:14 +0000 Subject: [PATCH 05/13] B-21458 remove debugging messages. --- pkg/notifications/move_payment_reminder.go | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/pkg/notifications/move_payment_reminder.go b/pkg/notifications/move_payment_reminder.go index ca9d560b12c..6a7e33b02cd 100644 --- a/pkg/notifications/move_payment_reminder.go +++ b/pkg/notifications/move_payment_reminder.go @@ -126,7 +126,7 @@ func (m PaymentReminder) formatEmails(appCtx appcontext.AppContext, PaymentRemin for _, PaymentReminderEmailInfo := range PaymentReminderEmailInfos { htmlBody, textBody, err := m.renderTemplates(appCtx, PaymentReminderEmailData{ OriginDutyLocation: PaymentReminderEmailInfo.OriginDutyLocationName, - DestinationLocation: getDestinationLocation(appCtx, PaymentReminderEmailInfo), + DestinationLocation: getDestinationLocation(PaymentReminderEmailInfo), Locator: PaymentReminderEmailInfo.Locator, OneSourceLink: OneSourceTransportationOfficeLink, MyMoveLink: MyMoveLink, @@ -168,23 +168,12 @@ func (m PaymentReminder) renderTemplates(appCtx appcontext.AppContext, data Paym return htmlBody, textBody, nil } -func getDestinationLocation(appCtx appcontext.AppContext, PaymentReminderEmailInfo PaymentReminderEmailInfo) string { +func getDestinationLocation(PaymentReminderEmailInfo PaymentReminderEmailInfo) string { destinationLocation := PaymentReminderEmailInfo.NewDutyLocationName ordersType := PaymentReminderEmailInfo.OrdersType street1 := PaymentReminderEmailInfo.DestinationStreet1 - if street1 != nil { - appCtx.Logger().Error("Street1 is: " + *street1) - } else { - appCtx.Logger().Error("Street1 is nil") - } isSeparateeOrRetireeOrder := ordersType == internalmessages.OrdersTypeRETIREMENT || ordersType == internalmessages.OrdersTypeSEPARATION - if isSeparateeOrRetireeOrder { - appCtx.Logger().Debug("isSeparateeOrRetireeOrder: true") - } else { - appCtx.Logger().Debug("isSeparateeOrRetireeOrder: false") - } if isSeparateeOrRetireeOrder && street1 != nil { - appCtx.Logger().Debug("In address section") street2, street3, city, state, postalCode := "", "", "", "", "" if PaymentReminderEmailInfo.DestinationStreet2 != nil { street2 = " " + *PaymentReminderEmailInfo.DestinationStreet2 @@ -202,7 +191,6 @@ func getDestinationLocation(appCtx appcontext.AppContext, PaymentReminderEmailIn postalCode = " " + *PaymentReminderEmailInfo.DestinationPostalCode } destinationLocation = fmt.Sprintf("%s%s%s%s%s%s", *street1, street2, street3, city, state, postalCode) - appCtx.Logger().Debug("New location: " + destinationLocation) } return destinationLocation } From d12e189dcd8a82138e5097276ee97dd6812afc39 Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:37:00 +0000 Subject: [PATCH 06/13] B-21458 handle PPM destination for move_submitted email, also standardize the address format with a new function in models/address.go with tests. --- pkg/models/address.go | 14 +++++++++++ pkg/models/address_test.go | 36 +++++++++++++++++++++++++++++ pkg/notifications/move_submitted.go | 18 +++++++-------- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/pkg/models/address.go b/pkg/models/address.go index 6fe61a0064d..b4577e1e2bd 100644 --- a/pkg/models/address.go +++ b/pkg/models/address.go @@ -128,6 +128,20 @@ func (a *Address) LineFormat() string { return strings.Join(parts, ", ") } +// LineDisplayFormat returns the address in a single line representation of the US mailing address format +func (a *Address) LineDisplayFormat() string { + optionalStreetAddress2 := "" + if a.StreetAddress2 != nil && len(*a.StreetAddress2) > 0 { + optionalStreetAddress2 = " " + *a.StreetAddress2 + } + optionalStreetAddress3 := "" + if a.StreetAddress3 != nil && len(*a.StreetAddress3) > 0 { + optionalStreetAddress3 = " " + *a.StreetAddress3 + } + + return fmt.Sprintf("%s%s%s, %s, %s %s", a.StreetAddress1, optionalStreetAddress2, optionalStreetAddress3, a.City, a.State, a.PostalCode) +} + // NotImplementedCountryCode is the default for unimplemented country code lookup type NotImplementedCountryCode struct { message string diff --git a/pkg/models/address_test.go b/pkg/models/address_test.go index 6b5e016911d..bc7c7312479 100644 --- a/pkg/models/address_test.go +++ b/pkg/models/address_test.go @@ -127,4 +127,40 @@ func (suite *ModelSuite) TestAddressFormat() { formattedAddress = newAddress.LineFormat() suite.Equal("street 1, street 2, street 3, city, state, 90210, UNITED STATES", formattedAddress) + + formattedAddress = newAddress.LineDisplayFormat() + + suite.Equal("street 1 street 2 street 3, city, state 90210", formattedAddress) +} + +func (suite *ModelSuite) TestPartialAddressFormat() { + country := factory.FetchOrBuildCountry(suite.DB(), nil, nil) + newAddress := &m.Address{ + StreetAddress1: "street 1", + StreetAddress2: nil, + StreetAddress3: nil, + City: "city", + State: "state", + PostalCode: "90210", + County: "County", + Country: &country, + CountryId: &country.ID, + } + + verrs, err := newAddress.Validate(nil) + + suite.NoError(err) + suite.False(verrs.HasAny(), "Error validating model") + + formattedAddress := newAddress.Format() + + suite.Equal("street 1\ncity, state 90210", formattedAddress) + + formattedAddress = newAddress.LineFormat() + + suite.Equal("street 1, city, state, 90210, UNITED STATES", formattedAddress) + + formattedAddress = newAddress.LineDisplayFormat() + + suite.Equal("street 1, city, state 90210", formattedAddress) } diff --git a/pkg/notifications/move_submitted.go b/pkg/notifications/move_submitted.go index 44bc484d46a..847ad84bbef 100644 --- a/pkg/notifications/move_submitted.go +++ b/pkg/notifications/move_submitted.go @@ -55,17 +55,15 @@ func (m MoveSubmitted) emails(appCtx appcontext.AppContext) ([]emailContent, err destinationAddress := orders.NewDutyLocation.Name isSeparateeRetiree := orders.OrdersType == internalmessages.OrdersTypeRETIREMENT || orders.OrdersType == internalmessages.OrdersTypeSEPARATION - if isSeparateeRetiree && len(move.MTOShipments) > 0 && move.MTOShipments[0].DestinationAddress != nil { - destAddr := *move.MTOShipments[0].DestinationAddress - optionalStreetAddress2 := "" - if destAddr.StreetAddress2 != nil { - optionalStreetAddress2 = " " + *destAddr.StreetAddress2 + if isSeparateeRetiree && len(move.MTOShipments) > 0 { + isPpmWithDestination := move.MTOShipments[0].PPMShipment != nil && move.MTOShipments[0].PPMShipment.DestinationAddress != nil + if isPpmWithDestination { + destAddr := *move.MTOShipments[0].PPMShipment.DestinationAddress + destinationAddress = destAddr.LineDisplayFormat() + } else if move.MTOShipments[0].DestinationAddress != nil { + destAddr := *move.MTOShipments[0].DestinationAddress + destinationAddress = destAddr.LineDisplayFormat() } - optionalStreetAddress3 := "" - if destAddr.StreetAddress3 != nil { - optionalStreetAddress3 = " " + *destAddr.StreetAddress3 - } - destinationAddress = fmt.Sprintf("%s%s%s, %s, %s %s", destAddr.StreetAddress1, optionalStreetAddress2, optionalStreetAddress3, destAddr.City, destAddr.State, destAddr.PostalCode) } originDutyLocation := orders.OriginDutyLocation From 920760d4d164d8c3ca9e4fffa5a53484a79cfeb9 Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Thu, 21 Nov 2024 23:35:53 +0000 Subject: [PATCH 07/13] B-21458 PPM destination fix for move_counseled email. --- pkg/notifications/move_counseled.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/notifications/move_counseled.go b/pkg/notifications/move_counseled.go index 4e2127a896b..27611207265 100644 --- a/pkg/notifications/move_counseled.go +++ b/pkg/notifications/move_counseled.go @@ -75,16 +75,16 @@ func (m MoveCounseled) emails(appCtx appcontext.AppContext) ([]emailContent, err } destinationAddress := orders.NewDutyLocation.Name - isSeparateeOrRetireeOrder := orders.OrdersType == internalmessages.OrdersTypeRETIREMENT || orders.OrdersType == internalmessages.OrdersTypeSEPARATION - if isSeparateeOrRetireeOrder && len(move.MTOShipments) > 0 && move.MTOShipments[0].DestinationAddress != nil { - mtoShipDestinationAddress, streetAddr2, streetAddr3 := *move.MTOShipments[0].DestinationAddress, "", "" - if mtoShipDestinationAddress.StreetAddress2 != nil { - streetAddr2 = " " + *mtoShipDestinationAddress.StreetAddress2 + isSeparateeRetiree := orders.OrdersType == internalmessages.OrdersTypeRETIREMENT || orders.OrdersType == internalmessages.OrdersTypeSEPARATION + if isSeparateeRetiree && len(move.MTOShipments) > 0 { + isPpmWithDestination := move.MTOShipments[0].PPMShipment != nil && move.MTOShipments[0].PPMShipment.DestinationAddress != nil + if isPpmWithDestination { + destAddr := *move.MTOShipments[0].PPMShipment.DestinationAddress + destinationAddress = destAddr.LineDisplayFormat() + } else if move.MTOShipments[0].DestinationAddress != nil { + destAddr := *move.MTOShipments[0].DestinationAddress + destinationAddress = destAddr.LineDisplayFormat() } - if mtoShipDestinationAddress.StreetAddress3 != nil { - streetAddr3 = " " + *mtoShipDestinationAddress.StreetAddress3 - } - destinationAddress = fmt.Sprintf("%s%s%s, %s, %s %s", mtoShipDestinationAddress.StreetAddress1, streetAddr2, streetAddr3, mtoShipDestinationAddress.City, mtoShipDestinationAddress.State, mtoShipDestinationAddress.PostalCode) } if serviceMember.PersonalEmail == nil { From c9ca88ca0111fb557b5514d22d8e7312e31d55f5 Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Sat, 30 Nov 2024 08:32:47 +0000 Subject: [PATCH 08/13] B-21458 new query for PPM data and updated logic to identify PPM, added first test which also works around an apparent issue with the PPM factory, wip. --- pkg/notifications/move_counseled.go | 38 ++++++++++--- pkg/notifications/move_submitted.go | 24 ++++++--- pkg/notifications/move_submitted_test.go | 68 ++++++++++++++++++++++++ 3 files changed, 116 insertions(+), 14 deletions(-) diff --git a/pkg/notifications/move_counseled.go b/pkg/notifications/move_counseled.go index 27611207265..a71aba5b29e 100644 --- a/pkg/notifications/move_counseled.go +++ b/pkg/notifications/move_counseled.go @@ -76,14 +76,18 @@ func (m MoveCounseled) emails(appCtx appcontext.AppContext) ([]emailContent, err destinationAddress := orders.NewDutyLocation.Name isSeparateeRetiree := orders.OrdersType == internalmessages.OrdersTypeRETIREMENT || orders.OrdersType == internalmessages.OrdersTypeSEPARATION - if isSeparateeRetiree && len(move.MTOShipments) > 0 { - isPpmWithDestination := move.MTOShipments[0].PPMShipment != nil && move.MTOShipments[0].PPMShipment.DestinationAddress != nil - if isPpmWithDestination { - destAddr := *move.MTOShipments[0].PPMShipment.DestinationAddress - destinationAddress = destAddr.LineDisplayFormat() - } else if move.MTOShipments[0].DestinationAddress != nil { - destAddr := *move.MTOShipments[0].DestinationAddress + if isSeparateeRetiree { + if len(move.MTOShipments) > 0 && move.MTOShipments[0].DestinationAddress != nil { + destAddr := move.MTOShipments[0].DestinationAddress destinationAddress = destAddr.LineDisplayFormat() + } else if len(*move.PPMType) > 0 { // TODO: use mtoShipment.ShipmentType == models.MTOShipmentTypePPM + destAddr, err := GetPpmDestinationAddress(appCtx, m.moveID) + if err != nil { + return emails, err + } + if len(destAddr) > 0 { + destinationAddress = destAddr[0].LineDisplayFormat() + } } } @@ -137,6 +141,26 @@ type MoveCounseledEmailData struct { ActualExpenseReimbursement bool } +func GetPpmDestinationAddress(appCtx appcontext.AppContext, moveId uuid.UUID) ([]models.Address, error) { + query := `SELECT + da.street_address_1, + da.street_address_2, + da.street_address_3, + da.city, + da.state, + da.postal_code +FROM mto_shipments ms + JOIN ppm_shipments ps on ms.id = ps.shipment_id + JOIN addresses da ON ps.destination_postal_address_id = da.id + WHERE ms.move_id = $1 + ` + + destinationAddresses := []models.Address{} + err := appCtx.DB().RawQuery(query, moveId).All(&destinationAddresses) + + return destinationAddresses, err +} + // RenderHTML renders the html for the email func (m MoveCounseled) RenderHTML(appCtx appcontext.AppContext, data MoveCounseledEmailData) (string, error) { var htmlBuffer bytes.Buffer diff --git a/pkg/notifications/move_submitted.go b/pkg/notifications/move_submitted.go index 847ad84bbef..a7559f8052b 100644 --- a/pkg/notifications/move_submitted.go +++ b/pkg/notifications/move_submitted.go @@ -4,6 +4,7 @@ import ( "bytes" "fmt" html "html/template" + "strconv" text "text/template" "github.com/dustin/go-humanize" @@ -55,14 +56,23 @@ func (m MoveSubmitted) emails(appCtx appcontext.AppContext) ([]emailContent, err destinationAddress := orders.NewDutyLocation.Name isSeparateeRetiree := orders.OrdersType == internalmessages.OrdersTypeRETIREMENT || orders.OrdersType == internalmessages.OrdersTypeSEPARATION - if isSeparateeRetiree && len(move.MTOShipments) > 0 { - isPpmWithDestination := move.MTOShipments[0].PPMShipment != nil && move.MTOShipments[0].PPMShipment.DestinationAddress != nil - if isPpmWithDestination { - destAddr := *move.MTOShipments[0].PPMShipment.DestinationAddress - destinationAddress = destAddr.LineDisplayFormat() - } else if move.MTOShipments[0].DestinationAddress != nil { - destAddr := *move.MTOShipments[0].DestinationAddress + if isSeparateeRetiree { + appCtx.Logger().Debug("MTOShipments: " + strconv.Itoa(len(move.MTOShipments))) + if len(move.MTOShipments) > 0 && move.MTOShipments[0].DestinationAddress != nil { + destAddr := move.MTOShipments[0].DestinationAddress destinationAddress = destAddr.LineDisplayFormat() + } else if len(*move.PPMType) > 0 { // TODO: use mtoShipment.ShipmentType == models.MTOShipmentTypePPM + destAddr, err := GetPpmDestinationAddress(appCtx, m.moveID) + if err != nil { + return emails, err + } + if len(destAddr) > 0 { + destinationAddress = destAddr[0].LineDisplayFormat() + } + + // TODO: use FetchAddressByID instead of custom SQL + //destAddr := models.FetchAddressByID(appCtx.DB(), move.MTOShipments[0].PPMShipment.DestinationAddressID) + //destinationAddress = destAddr.LineDisplayFormat() } } diff --git a/pkg/notifications/move_submitted_test.go b/pkg/notifications/move_submitted_test.go index f2a48684880..2f0d70d68a6 100644 --- a/pkg/notifications/move_submitted_test.go +++ b/pkg/notifications/move_submitted_test.go @@ -149,6 +149,74 @@ func (suite *NotificationSuite) TestMoveSubmittedDestinationIsDutyStationForPcsT suite.NotContains(email.textBody, *move.MTOShipments[0].DestinationAddress.StreetAddress3) } +func (suite *NotificationSuite) TestMoveSubmittedDestinationIsShipmentForPpmSeparatee() { + appCtx := suite.AppContextForTest() + + // TODO: move PPM build + //move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ + builtPpmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ + { + Model: models.Move{ + //SubmittedAt: &now, + //Status: models.MoveStatusSUBMITTED, + PPMType: models.StringPointer("FULL"), + }, + }, + { + Model: models.Order{ + OrdersType: internalmessages.OrdersTypeSEPARATION, + }, + }, + }, nil) + + //ppmShipment := BuildPPMShipment(db, customs, traits) + builtMtoShipment := builtPpmShipment.Shipment + move := builtMtoShipment.MoveTaskOrder + move.MTOShipments = append(move.MTOShipments, builtMtoShipment) + + appCtx.Logger().Debug("moveID: " + move.ID.String()) + appCtx.Logger().Debug("MTO MoveID: " + move.MTOShipments[0].MoveTaskOrderID.String()) + appCtx.Logger().Debug("MTO ShipmentID: " + move.MTOShipments[0].ID.String()) + appCtx.Logger().Debug("PPM ShipmentID: " + move.MTOShipments[0].PPMShipment.ShipmentID.String()) + isSeparateeRetiree := move.Orders.OrdersType == internalmessages.OrdersTypeRETIREMENT || move.Orders.OrdersType == internalmessages.OrdersTypeSEPARATION + if isSeparateeRetiree { + appCtx.Logger().Debug("is SeparateeRetiree") + } else { + appCtx.Logger().Debug("NOT SeparateeRetiree") + } + notification := NewMoveSubmitted(move.ID) + + emails, err := notification.emails(suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: move.Orders.ServiceMember.ID, + ApplicationName: auth.MilApp, + })) + subject := "Thank you for submitting your move details" + + suite.NoError(err) + suite.Equal(len(emails), 1) + + email := emails[0] + sm := move.Orders.ServiceMember + mtoShipment := move.MTOShipments[0] + ppmShipment := mtoShipment.PPMShipment + destinationAddress := ppmShipment.DestinationAddress + suite.Equal(email.recipientEmail, *sm.PersonalEmail) + suite.Equal(email.subject, subject) + suite.NotEmpty(email.htmlBody) + suite.NotEmpty(email.textBody) + appCtx.Logger().Debug("Looking for destinationAddress: " + destinationAddress.LineDisplayFormat()) + suite.Contains(email.textBody, "details for your move from "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+".") +} + +func (suite *NotificationSuite) TestMoveSubmittedDestinationIsShipmentForPpmRetiree() { +} + +func (suite *NotificationSuite) TestMoveSubmittedDestinationIsDutyStationForPpmRetireeWithoutAddress() { +} + +func (suite *NotificationSuite) TestMoveSubmittedDestinationIsDutyStationForPpmPcsType() { +} + func (suite *NotificationSuite) TestMoveSubmittedHTMLTemplateRenderWithGovCounseling() { approver := factory.BuildUser(nil, nil, nil) move := factory.BuildMove(suite.DB(), nil, nil) From c00177c23f3dcf19f8327162407837da971a6771 Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Mon, 2 Dec 2024 16:32:56 +0000 Subject: [PATCH 09/13] B-21458 expanded and improved unit tests for PPM reminder email. --- pkg/notifications/move_submitted_test.go | 87 +++++++++++++++--------- 1 file changed, 55 insertions(+), 32 deletions(-) diff --git a/pkg/notifications/move_submitted_test.go b/pkg/notifications/move_submitted_test.go index 2f0d70d68a6..25b9ae9a100 100644 --- a/pkg/notifications/move_submitted_test.go +++ b/pkg/notifications/move_submitted_test.go @@ -149,48 +149,30 @@ func (suite *NotificationSuite) TestMoveSubmittedDestinationIsDutyStationForPcsT suite.NotContains(email.textBody, *move.MTOShipments[0].DestinationAddress.StreetAddress3) } -func (suite *NotificationSuite) TestMoveSubmittedDestinationIsShipmentForPpmSeparatee() { - appCtx := suite.AppContextForTest() - - // TODO: move PPM build - //move := factory.BuildMoveWithPPMShipment(suite.DB(), []factory.Customization{ +func SetupPpmMove(suite *NotificationSuite, ordersType internalmessages.OrdersType) models.Move { builtPpmShipment := factory.BuildPPMShipment(suite.DB(), []factory.Customization{ - { - Model: models.Move{ - //SubmittedAt: &now, - //Status: models.MoveStatusSUBMITTED, - PPMType: models.StringPointer("FULL"), - }, - }, { Model: models.Order{ - OrdersType: internalmessages.OrdersTypeSEPARATION, + OrdersType: ordersType, }, }, }, nil) - - //ppmShipment := BuildPPMShipment(db, customs, traits) builtMtoShipment := builtPpmShipment.Shipment move := builtMtoShipment.MoveTaskOrder move.MTOShipments = append(move.MTOShipments, builtMtoShipment) - appCtx.Logger().Debug("moveID: " + move.ID.String()) - appCtx.Logger().Debug("MTO MoveID: " + move.MTOShipments[0].MoveTaskOrderID.String()) - appCtx.Logger().Debug("MTO ShipmentID: " + move.MTOShipments[0].ID.String()) - appCtx.Logger().Debug("PPM ShipmentID: " + move.MTOShipments[0].PPMShipment.ShipmentID.String()) - isSeparateeRetiree := move.Orders.OrdersType == internalmessages.OrdersTypeRETIREMENT || move.Orders.OrdersType == internalmessages.OrdersTypeSEPARATION - if isSeparateeRetiree { - appCtx.Logger().Debug("is SeparateeRetiree") - } else { - appCtx.Logger().Debug("NOT SeparateeRetiree") - } + return move +} + +func (suite *NotificationSuite) TestMoveSubmittedDestinationIsShipmentForPpmSeparatee() { + move := SetupPpmMove(suite, internalmessages.OrdersTypeSEPARATION) notification := NewMoveSubmitted(move.ID) + expectedSubject := "Thank you for submitting your move details" emails, err := notification.emails(suite.AppContextWithSessionForTest(&auth.Session{ ServiceMemberID: move.Orders.ServiceMember.ID, ApplicationName: auth.MilApp, })) - subject := "Thank you for submitting your move details" suite.NoError(err) suite.Equal(len(emails), 1) @@ -201,20 +183,61 @@ func (suite *NotificationSuite) TestMoveSubmittedDestinationIsShipmentForPpmSepa ppmShipment := mtoShipment.PPMShipment destinationAddress := ppmShipment.DestinationAddress suite.Equal(email.recipientEmail, *sm.PersonalEmail) - suite.Equal(email.subject, subject) - suite.NotEmpty(email.htmlBody) - suite.NotEmpty(email.textBody) - appCtx.Logger().Debug("Looking for destinationAddress: " + destinationAddress.LineDisplayFormat()) + suite.Equal(email.subject, expectedSubject) + suite.Contains(email.htmlBody, "details for your move from\n "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+".") suite.Contains(email.textBody, "details for your move from "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+".") } func (suite *NotificationSuite) TestMoveSubmittedDestinationIsShipmentForPpmRetiree() { -} + move := SetupPpmMove(suite, internalmessages.OrdersTypeRETIREMENT) + notification := NewMoveSubmitted(move.ID) + expectedSubject := "Thank you for submitting your move details" + + emails, err := notification.emails(suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: move.Orders.ServiceMember.ID, + ApplicationName: auth.MilApp, + })) + + suite.NoError(err) + suite.Equal(len(emails), 1) -func (suite *NotificationSuite) TestMoveSubmittedDestinationIsDutyStationForPpmRetireeWithoutAddress() { + email := emails[0] + sm := move.Orders.ServiceMember + mtoShipment := move.MTOShipments[0] + ppmShipment := mtoShipment.PPMShipment + destinationAddress := ppmShipment.DestinationAddress + suite.Equal(email.recipientEmail, *sm.PersonalEmail) + suite.Equal(email.subject, expectedSubject) + suite.NotEmpty(email.htmlBody) + suite.Contains(email.htmlBody, "details for your move from\n "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+".") + suite.Contains(email.textBody, "details for your move from "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+".") } func (suite *NotificationSuite) TestMoveSubmittedDestinationIsDutyStationForPpmPcsType() { + move := SetupPpmMove(suite, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + notification := NewMoveSubmitted(move.ID) + expectedSubject := "Thank you for submitting your move details" + + emails, err := notification.emails(suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: move.Orders.ServiceMember.ID, + ApplicationName: auth.MilApp, + })) + + suite.NoError(err) + suite.Equal(len(emails), 1) + + email := emails[0] + sm := move.Orders.ServiceMember + mtoShipment := move.MTOShipments[0] + ppmShipment := mtoShipment.PPMShipment + destinationAddress := ppmShipment.DestinationAddress + suite.Equal(email.recipientEmail, *sm.PersonalEmail) + suite.Equal(email.subject, expectedSubject) + suite.NotEmpty(email.htmlBody) + suite.NotContains(email.htmlBody, destinationAddress.LineDisplayFormat()) + suite.NotContains(email.textBody, destinationAddress.LineDisplayFormat()) + suite.Contains(email.htmlBody, "details for your move from\n "+move.Orders.OriginDutyLocation.Name+" to "+move.Orders.NewDutyLocation.Name+".") + suite.Contains(email.textBody, "details for your move from "+move.Orders.OriginDutyLocation.Name+" to "+move.Orders.NewDutyLocation.Name+".") } func (suite *NotificationSuite) TestMoveSubmittedHTMLTemplateRenderWithGovCounseling() { From d825891f489019984bf7efc96a1af80eb2e29fc8 Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:32:24 +0000 Subject: [PATCH 10/13] B-21458 replace SQL query with getAddress call, also expand unit tests to include the move_counseled email for PPMs. --- pkg/notifications/move_counseled.go | 29 +-------- pkg/notifications/move_counseled_test.go | 76 ++++++++++++++++++++++++ pkg/notifications/move_submitted.go | 13 +--- 3 files changed, 80 insertions(+), 38 deletions(-) diff --git a/pkg/notifications/move_counseled.go b/pkg/notifications/move_counseled.go index a71aba5b29e..ea443c96022 100644 --- a/pkg/notifications/move_counseled.go +++ b/pkg/notifications/move_counseled.go @@ -81,13 +81,8 @@ func (m MoveCounseled) emails(appCtx appcontext.AppContext) ([]emailContent, err destAddr := move.MTOShipments[0].DestinationAddress destinationAddress = destAddr.LineDisplayFormat() } else if len(*move.PPMType) > 0 { // TODO: use mtoShipment.ShipmentType == models.MTOShipmentTypePPM - destAddr, err := GetPpmDestinationAddress(appCtx, m.moveID) - if err != nil { - return emails, err - } - if len(destAddr) > 0 { - destinationAddress = destAddr[0].LineDisplayFormat() - } + destAddr := models.FetchAddressByID(appCtx.DB(), move.MTOShipments[0].PPMShipment.DestinationAddressID) + destinationAddress = destAddr.LineDisplayFormat() } } @@ -141,26 +136,6 @@ type MoveCounseledEmailData struct { ActualExpenseReimbursement bool } -func GetPpmDestinationAddress(appCtx appcontext.AppContext, moveId uuid.UUID) ([]models.Address, error) { - query := `SELECT - da.street_address_1, - da.street_address_2, - da.street_address_3, - da.city, - da.state, - da.postal_code -FROM mto_shipments ms - JOIN ppm_shipments ps on ms.id = ps.shipment_id - JOIN addresses da ON ps.destination_postal_address_id = da.id - WHERE ms.move_id = $1 - ` - - destinationAddresses := []models.Address{} - err := appCtx.DB().RawQuery(query, moveId).All(&destinationAddresses) - - return destinationAddresses, err -} - // RenderHTML renders the html for the email func (m MoveCounseled) RenderHTML(appCtx appcontext.AppContext, data MoveCounseledEmailData) (string, error) { var htmlBuffer bytes.Buffer diff --git a/pkg/notifications/move_counseled_test.go b/pkg/notifications/move_counseled_test.go index 047d7f9ba30..493da449dca 100644 --- a/pkg/notifications/move_counseled_test.go +++ b/pkg/notifications/move_counseled_test.go @@ -199,3 +199,79 @@ func (suite *NotificationSuite) TestCounselorApprovedMoveForRetiree() { suite.Contains(email.textBody, move.MTOShipments[0].DestinationAddress.State) suite.Contains(email.textBody, move.MTOShipments[0].DestinationAddress.PostalCode) } + +func (suite *NotificationSuite) TestMoveCounseledDestinationIsShipmentForPpmSeparatee() { + move := SetupPpmMove(suite, internalmessages.OrdersTypeSEPARATION) + notification := NewMoveCounseled(move.ID) + expectedSubject := "Your counselor has approved your move details" + + emails, err := notification.emails(suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: move.Orders.ServiceMember.ID, + ApplicationName: auth.MilApp, + })) + + suite.NoError(err) + suite.Equal(len(emails), 1) + + email := emails[0] + sm := move.Orders.ServiceMember + mtoShipment := move.MTOShipments[0] + ppmShipment := mtoShipment.PPMShipment + destinationAddress := ppmShipment.DestinationAddress + suite.Equal(email.recipientEmail, *sm.PersonalEmail) + suite.Equal(email.subject, expectedSubject) + suite.Contains(email.htmlBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+" in the ") + suite.Contains(email.textBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+" in the ") +} + +func (suite *NotificationSuite) TestMoveCounseledDestinationIsShipmentForPpmRetiree() { + move := SetupPpmMove(suite, internalmessages.OrdersTypeRETIREMENT) + notification := NewMoveCounseled(move.ID) + expectedSubject := "Your counselor has approved your move details" + + emails, err := notification.emails(suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: move.Orders.ServiceMember.ID, + ApplicationName: auth.MilApp, + })) + + suite.NoError(err) + suite.Equal(len(emails), 1) + + email := emails[0] + sm := move.Orders.ServiceMember + mtoShipment := move.MTOShipments[0] + ppmShipment := mtoShipment.PPMShipment + destinationAddress := ppmShipment.DestinationAddress + suite.Equal(email.recipientEmail, *sm.PersonalEmail) + suite.Equal(email.subject, expectedSubject) + suite.NotEmpty(email.htmlBody) + suite.Contains(email.htmlBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+" in the ") + suite.Contains(email.textBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+" in the ") +} + +func (suite *NotificationSuite) TestMoveCounseledDestinationIsDutyStationForPpmPcsType() { + move := SetupPpmMove(suite, internalmessages.OrdersTypePERMANENTCHANGEOFSTATION) + notification := NewMoveCounseled(move.ID) + expectedSubject := "Your counselor has approved your move details" + + emails, err := notification.emails(suite.AppContextWithSessionForTest(&auth.Session{ + ServiceMemberID: move.Orders.ServiceMember.ID, + ApplicationName: auth.MilApp, + })) + + suite.NoError(err) + suite.Equal(len(emails), 1) + + email := emails[0] + sm := move.Orders.ServiceMember + mtoShipment := move.MTOShipments[0] + ppmShipment := mtoShipment.PPMShipment + destinationAddress := ppmShipment.DestinationAddress + suite.Equal(email.recipientEmail, *sm.PersonalEmail) + suite.Equal(email.subject, expectedSubject) + suite.NotEmpty(email.htmlBody) + suite.NotContains(email.htmlBody, destinationAddress.LineDisplayFormat()) + suite.NotContains(email.textBody, destinationAddress.LineDisplayFormat()) + suite.Contains(email.htmlBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+move.Orders.NewDutyLocation.Name+" in the ") + suite.Contains(email.textBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+move.Orders.NewDutyLocation.Name+" in the ") +} diff --git a/pkg/notifications/move_submitted.go b/pkg/notifications/move_submitted.go index a7559f8052b..11b2896713f 100644 --- a/pkg/notifications/move_submitted.go +++ b/pkg/notifications/move_submitted.go @@ -62,17 +62,8 @@ func (m MoveSubmitted) emails(appCtx appcontext.AppContext) ([]emailContent, err destAddr := move.MTOShipments[0].DestinationAddress destinationAddress = destAddr.LineDisplayFormat() } else if len(*move.PPMType) > 0 { // TODO: use mtoShipment.ShipmentType == models.MTOShipmentTypePPM - destAddr, err := GetPpmDestinationAddress(appCtx, m.moveID) - if err != nil { - return emails, err - } - if len(destAddr) > 0 { - destinationAddress = destAddr[0].LineDisplayFormat() - } - - // TODO: use FetchAddressByID instead of custom SQL - //destAddr := models.FetchAddressByID(appCtx.DB(), move.MTOShipments[0].PPMShipment.DestinationAddressID) - //destinationAddress = destAddr.LineDisplayFormat() + destAddr := models.FetchAddressByID(appCtx.DB(), move.MTOShipments[0].PPMShipment.DestinationAddressID) + destinationAddress = destAddr.LineDisplayFormat() } } From 3aa8ca3cb29f8ba53519c7b09a1ba05e6833b52c Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Tue, 3 Dec 2024 19:59:13 +0000 Subject: [PATCH 11/13] B-21458 refactor PPM Address logic for Submitted and Counseled emails. --- pkg/notifications/move_counseled.go | 11 ++++++----- pkg/notifications/move_submitted.go | 13 ++++++------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/pkg/notifications/move_counseled.go b/pkg/notifications/move_counseled.go index ea443c96022..e519fdcc6a7 100644 --- a/pkg/notifications/move_counseled.go +++ b/pkg/notifications/move_counseled.go @@ -76,12 +76,13 @@ func (m MoveCounseled) emails(appCtx appcontext.AppContext) ([]emailContent, err destinationAddress := orders.NewDutyLocation.Name isSeparateeRetiree := orders.OrdersType == internalmessages.OrdersTypeRETIREMENT || orders.OrdersType == internalmessages.OrdersTypeSEPARATION - if isSeparateeRetiree { - if len(move.MTOShipments) > 0 && move.MTOShipments[0].DestinationAddress != nil { - destAddr := move.MTOShipments[0].DestinationAddress + if isSeparateeRetiree && len(move.MTOShipments) > 0 { + mtoShipment := move.MTOShipments[0] + if mtoShipment.DestinationAddress != nil { + destAddr := mtoShipment.DestinationAddress destinationAddress = destAddr.LineDisplayFormat() - } else if len(*move.PPMType) > 0 { // TODO: use mtoShipment.ShipmentType == models.MTOShipmentTypePPM - destAddr := models.FetchAddressByID(appCtx.DB(), move.MTOShipments[0].PPMShipment.DestinationAddressID) + } else if mtoShipment.ShipmentType == models.MTOShipmentTypePPM { + destAddr := models.FetchAddressByID(appCtx.DB(), mtoShipment.PPMShipment.DestinationAddressID) destinationAddress = destAddr.LineDisplayFormat() } } diff --git a/pkg/notifications/move_submitted.go b/pkg/notifications/move_submitted.go index 11b2896713f..2919deb2e54 100644 --- a/pkg/notifications/move_submitted.go +++ b/pkg/notifications/move_submitted.go @@ -4,7 +4,6 @@ import ( "bytes" "fmt" html "html/template" - "strconv" text "text/template" "github.com/dustin/go-humanize" @@ -56,13 +55,13 @@ func (m MoveSubmitted) emails(appCtx appcontext.AppContext) ([]emailContent, err destinationAddress := orders.NewDutyLocation.Name isSeparateeRetiree := orders.OrdersType == internalmessages.OrdersTypeRETIREMENT || orders.OrdersType == internalmessages.OrdersTypeSEPARATION - if isSeparateeRetiree { - appCtx.Logger().Debug("MTOShipments: " + strconv.Itoa(len(move.MTOShipments))) - if len(move.MTOShipments) > 0 && move.MTOShipments[0].DestinationAddress != nil { - destAddr := move.MTOShipments[0].DestinationAddress + if isSeparateeRetiree && len(move.MTOShipments) > 0 { + mtoShipment := move.MTOShipments[0] + if mtoShipment.DestinationAddress != nil { + destAddr := mtoShipment.DestinationAddress destinationAddress = destAddr.LineDisplayFormat() - } else if len(*move.PPMType) > 0 { // TODO: use mtoShipment.ShipmentType == models.MTOShipmentTypePPM - destAddr := models.FetchAddressByID(appCtx.DB(), move.MTOShipments[0].PPMShipment.DestinationAddressID) + } else if mtoShipment.ShipmentType == models.MTOShipmentTypePPM { + destAddr := models.FetchAddressByID(appCtx.DB(), mtoShipment.PPMShipment.DestinationAddressID) destinationAddress = destAddr.LineDisplayFormat() } } From 6095b3302c3041f4eb573da288982c20f7cf3f30 Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Fri, 6 Dec 2024 19:58:48 +0000 Subject: [PATCH 12/13] B-21458 cleanup tests, remove redundant NotEmpty check, already checking with Contains. --- pkg/notifications/move_counseled_test.go | 2 -- pkg/notifications/move_submitted_test.go | 2 -- 2 files changed, 4 deletions(-) diff --git a/pkg/notifications/move_counseled_test.go b/pkg/notifications/move_counseled_test.go index 493da449dca..a66370c27af 100644 --- a/pkg/notifications/move_counseled_test.go +++ b/pkg/notifications/move_counseled_test.go @@ -244,7 +244,6 @@ func (suite *NotificationSuite) TestMoveCounseledDestinationIsShipmentForPpmReti destinationAddress := ppmShipment.DestinationAddress suite.Equal(email.recipientEmail, *sm.PersonalEmail) suite.Equal(email.subject, expectedSubject) - suite.NotEmpty(email.htmlBody) suite.Contains(email.htmlBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+" in the ") suite.Contains(email.textBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+" in the ") } @@ -269,7 +268,6 @@ func (suite *NotificationSuite) TestMoveCounseledDestinationIsDutyStationForPpmP destinationAddress := ppmShipment.DestinationAddress suite.Equal(email.recipientEmail, *sm.PersonalEmail) suite.Equal(email.subject, expectedSubject) - suite.NotEmpty(email.htmlBody) suite.NotContains(email.htmlBody, destinationAddress.LineDisplayFormat()) suite.NotContains(email.textBody, destinationAddress.LineDisplayFormat()) suite.Contains(email.htmlBody, "from "+move.Orders.OriginDutyLocation.Name+" to "+move.Orders.NewDutyLocation.Name+" in the ") diff --git a/pkg/notifications/move_submitted_test.go b/pkg/notifications/move_submitted_test.go index 25b9ae9a100..b636d26a536 100644 --- a/pkg/notifications/move_submitted_test.go +++ b/pkg/notifications/move_submitted_test.go @@ -208,7 +208,6 @@ func (suite *NotificationSuite) TestMoveSubmittedDestinationIsShipmentForPpmReti destinationAddress := ppmShipment.DestinationAddress suite.Equal(email.recipientEmail, *sm.PersonalEmail) suite.Equal(email.subject, expectedSubject) - suite.NotEmpty(email.htmlBody) suite.Contains(email.htmlBody, "details for your move from\n "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+".") suite.Contains(email.textBody, "details for your move from "+move.Orders.OriginDutyLocation.Name+" to "+destinationAddress.LineDisplayFormat()+".") } @@ -233,7 +232,6 @@ func (suite *NotificationSuite) TestMoveSubmittedDestinationIsDutyStationForPpmP destinationAddress := ppmShipment.DestinationAddress suite.Equal(email.recipientEmail, *sm.PersonalEmail) suite.Equal(email.subject, expectedSubject) - suite.NotEmpty(email.htmlBody) suite.NotContains(email.htmlBody, destinationAddress.LineDisplayFormat()) suite.NotContains(email.textBody, destinationAddress.LineDisplayFormat()) suite.Contains(email.htmlBody, "details for your move from\n "+move.Orders.OriginDutyLocation.Name+" to "+move.Orders.NewDutyLocation.Name+".") From 92f1211b59db2e99880c3a2d74df36982c482af2 Mon Sep 17 00:00:00 2001 From: Steven Gleason <180579696+stevengleason-caci@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:09:06 +0000 Subject: [PATCH 13/13] B-21458 update address test for County pointer. --- pkg/models/address_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/models/address_test.go b/pkg/models/address_test.go index d8f34151749..f2fbb5bf45c 100644 --- a/pkg/models/address_test.go +++ b/pkg/models/address_test.go @@ -168,7 +168,7 @@ func (suite *ModelSuite) TestPartialAddressFormat() { City: "city", State: "state", PostalCode: "90210", - County: "County", + County: m.StringPointer("County"), Country: &country, CountryId: &country.ID, }