Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

B 20939 int #13538

Merged
merged 13 commits into from
Aug 23, 2024
Binary file modified pkg/assets/paperwork/formtemplates/SSWPDFTemplate.pdf
Binary file not shown.
4 changes: 2 additions & 2 deletions pkg/services/shipment_summary_worksheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ type Page1Values struct {
SITEntryDates string
SITEndDates string
SITDaysInStorage string
PreparationDate string
PreparationDate1 string
MaxObligationGCC100 string
TotalWeightAllotmentRepeat string
MaxObligationGCC95 string
Expand All @@ -64,7 +64,7 @@ type Page1Values struct {
// Page2Values is an object representing a Shipment Summary Worksheet
type Page2Values struct {
CUIBanner string
PreparationDate string
PreparationDate2 string
TAC string
SAC string
ContractedExpenseMemberPaid string
Expand Down
130 changes: 80 additions & 50 deletions pkg/services/shipment_summary_worksheet/shipment_summary_worksheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,6 @@ func FormatValuesShipmentSummaryWorksheetFormPage1(data services.ShipmentSummary
page1.MaxSITStorageEntitlement = fmt.Sprintf("%02d Days in SIT", data.MaxSITStorageEntitlement)
// We don't currently know what allows POV to be authorized, so we are hardcoding it to "No" to start
page1.POVAuthorized = "No"
page1.PreparationDate = FormatDate(data.PreparationDate)

sm := data.ServiceMember
page1.ServiceMemberName = FormatServiceMemberFullName(sm)
Expand Down Expand Up @@ -247,9 +246,11 @@ func FormatValuesShipmentSummaryWorksheetFormPage1(data services.ShipmentSummary
finalPPMWeight := FormatPPMWeightFinal(data.PPMShipmentFinalWeight)
page1.ShipmentWeights = finalPPMWeight
page1.ActualObligationGCC100 = finalPPMWeight + "; " + formattedShipment.FinalIncentive
page1.PreparationDate1 = formatSSWDate(data.SignedCertifications, data.PPMShipment.ID)
} else {
page1.ShipmentWeights = formattedShipments.ShipmentWeights
page1.ActualObligationGCC100 = formattedShipments.ShipmentWeightForObligation + " - Estimated lbs; " + formattedShipment.FinalIncentive
page1.PreparationDate1 = formatAOADate(data.SignedCertifications, data.PPMShipment.ID)
}
page1.MaxObligationGCC100 = FormatWeights(data.WeightAllotment.TotalWeight) + " lbs; " + formattedShipment.EstimatedIncentive
page1.MaxObligationGCCMaxAdvance = formattedShipment.MaxAdvance
Expand All @@ -261,45 +262,6 @@ func FormatValuesShipmentSummaryWorksheetFormPage1(data services.ShipmentSummary
return page1
}

// FormatGrade formats the service member's rank for Shipment Summary Worksheet
func FormatGrade(grade *internalmessages.OrderPayGrade) string {
var gradeDisplayValue = map[internalmessages.OrderPayGrade]string{
models.ServiceMemberGradeE1: "E-1",
models.ServiceMemberGradeE2: "E-2",
models.ServiceMemberGradeE3: "E-3",
models.ServiceMemberGradeE4: "E-4",
models.ServiceMemberGradeE5: "E-5",
models.ServiceMemberGradeE6: "E-6",
models.ServiceMemberGradeE7: "E-7",
models.ServiceMemberGradeE8: "E-8",
models.ServiceMemberGradeE9: "E-9",
models.ServiceMemberGradeE9SPECIALSENIORENLISTED: "E-9 (Special Senior Enlisted)",
models.ServiceMemberGradeO1ACADEMYGRADUATE: "O-1 or Service Academy Graduate",
models.ServiceMemberGradeO2: "O-2",
models.ServiceMemberGradeO3: "O-3",
models.ServiceMemberGradeO4: "O-4",
models.ServiceMemberGradeO5: "O-5",
models.ServiceMemberGradeO6: "O-6",
models.ServiceMemberGradeO7: "O-7",
models.ServiceMemberGradeO8: "O-8",
models.ServiceMemberGradeO9: "O-9",
models.ServiceMemberGradeO10: "O-10",
models.ServiceMemberGradeW1: "W-1",
models.ServiceMemberGradeW2: "W-2",
models.ServiceMemberGradeW3: "W-3",
models.ServiceMemberGradeW4: "W-4",
models.ServiceMemberGradeW5: "W-5",
models.ServiceMemberGradeAVIATIONCADET: "Aviation Cadet",
models.ServiceMemberGradeCIVILIANEMPLOYEE: "Civilian Employee",
models.ServiceMemberGradeACADEMYCADET: "Service Academy Cadet",
models.ServiceMemberGradeMIDSHIPMAN: "Midshipman",
}
if grade != nil {
return gradeDisplayValue[*grade]
}
return ""
}

// FormatValuesShipmentSummaryWorksheetFormPage2 formats the data for page 2 of the Shipment Summary Worksheet
func FormatValuesShipmentSummaryWorksheetFormPage2(data services.ShipmentSummaryFormData, isPaymentPacket bool) services.Page2Values {

Expand All @@ -311,7 +273,11 @@ func FormatValuesShipmentSummaryWorksheetFormPage2(data services.ShipmentSummary
page2.CUIBanner = controlledUnclassifiedInformationText
page2.TAC = derefStringTypes(data.Order.TAC)
page2.SAC = derefStringTypes(data.Order.SAC)
page2.PreparationDate = FormatDate(data.PreparationDate)
if isPaymentPacket {
page2.PreparationDate2 = formatSSWDate(data.SignedCertifications, data.PPMShipment.ID)
} else {
page2.PreparationDate2 = formatAOADate(data.SignedCertifications, data.PPMShipment.ID)
}
page2.ContractedExpenseMemberPaid = FormatDollars(expensesMap["ContractedExpenseMemberPaid"])
page2.ContractedExpenseGTCCPaid = FormatDollars(expensesMap["ContractedExpenseGTCCPaid"])
page2.PackingMaterialsMemberPaid = FormatDollars(expensesMap["PackingMaterialsMemberPaid"])
Expand Down Expand Up @@ -342,6 +308,45 @@ func FormatValuesShipmentSummaryWorksheetFormPage2(data services.ShipmentSummary
return page2
}

// FormatGrade formats the service member's rank for Shipment Summary Worksheet
func FormatGrade(grade *internalmessages.OrderPayGrade) string {
var gradeDisplayValue = map[internalmessages.OrderPayGrade]string{
models.ServiceMemberGradeE1: "E-1",
models.ServiceMemberGradeE2: "E-2",
models.ServiceMemberGradeE3: "E-3",
models.ServiceMemberGradeE4: "E-4",
models.ServiceMemberGradeE5: "E-5",
models.ServiceMemberGradeE6: "E-6",
models.ServiceMemberGradeE7: "E-7",
models.ServiceMemberGradeE8: "E-8",
models.ServiceMemberGradeE9: "E-9",
models.ServiceMemberGradeE9SPECIALSENIORENLISTED: "E-9 (Special Senior Enlisted)",
models.ServiceMemberGradeO1ACADEMYGRADUATE: "O-1 or Service Academy Graduate",
models.ServiceMemberGradeO2: "O-2",
models.ServiceMemberGradeO3: "O-3",
models.ServiceMemberGradeO4: "O-4",
models.ServiceMemberGradeO5: "O-5",
models.ServiceMemberGradeO6: "O-6",
models.ServiceMemberGradeO7: "O-7",
models.ServiceMemberGradeO8: "O-8",
models.ServiceMemberGradeO9: "O-9",
models.ServiceMemberGradeO10: "O-10",
models.ServiceMemberGradeW1: "W-1",
models.ServiceMemberGradeW2: "W-2",
models.ServiceMemberGradeW3: "W-3",
models.ServiceMemberGradeW4: "W-4",
models.ServiceMemberGradeW5: "W-5",
models.ServiceMemberGradeAVIATIONCADET: "Aviation Cadet",
models.ServiceMemberGradeCIVILIANEMPLOYEE: "Civilian Employee",
models.ServiceMemberGradeACADEMYCADET: "Service Academy Cadet",
models.ServiceMemberGradeMIDSHIPMAN: "Midshipman",
}
if grade != nil {
return gradeDisplayValue[*grade]
}
return ""
}

func formatEmplid(serviceMember models.ServiceMember) (*string, error) {
const prefix = "EMPLID:"
const separator = " "
Expand Down Expand Up @@ -383,10 +388,10 @@ func formatSignedCertifications(signedCertifications []*models.SignedCertificati
switch {
case *cert.CertificationType == models.SignedCertificationTypePreCloseoutReviewedPPMPAYMENT:
aoaSignature = cert.Signature
aoaDate = FormatSignatureDate(cert.UpdatedAt) // We use updatedat to get the most recent signature dates
aoaDate = FormatDate(cert.UpdatedAt) // We use updatedat to get the most recent signature dates
case *cert.CertificationType == models.SignedCertificationTypeCloseoutReviewedPPMPAYMENT:
sswSignature = cert.Signature
sswDate = FormatSignatureDate(cert.UpdatedAt) // We use updatedat to get the most recent signature dates
sswDate = FormatDate(cert.UpdatedAt) // We use updatedat to get the most recent signature dates
}
}
}
Expand All @@ -397,11 +402,36 @@ func formatSignedCertifications(signedCertifications []*models.SignedCertificati
return certifications
}

// FormatSignatureDate formats the date the office members signed the SSW
func FormatSignatureDate(signature time.Time) string {
dateLayout := "02 Jan 2006" // Removed time to save space on template, per PO it's not needed
dt := signature.Format(dateLayout)
return dt
// The following formats the preparation date, as the preparation date for AOAs is the date the service counselor certifies the advance.
func formatAOADate(signedCertifications []*models.SignedCertification, ppmid uuid.UUID) string {
// This loop evaluates certs to find Office AOA Signature date
for _, cert := range signedCertifications {
if cert.PpmID != nil { // Required to avoid error, service members signatures have nil ppm ids
if *cert.PpmID == ppmid { // PPM ID needs to be checked to prevent signatures from other PPMs on the same move from populating
if *cert.CertificationType == models.SignedCertificationTypePreCloseoutReviewedPPMPAYMENT {
aoaDate := FormatDate(cert.UpdatedAt) // We use updatedat to get the most recent signature dates
return aoaDate
}
}
}
}
return FormatDate(time.Now())
}

// The following formats the preparation date, as the preparation date for SSWs is the date the closeout counselor certifies the closeout.
func formatSSWDate(signedCertifications []*models.SignedCertification, ppmid uuid.UUID) string {
// This loop evaluates certs to find Office SSW Signature date
for _, cert := range signedCertifications {
if cert.PpmID != nil { // Required to avoid error, service members signatures have nil ppm ids
if *cert.PpmID == ppmid { // PPM ID needs to be checked to prevent signatures from other PPMs on the same move from populating
if *cert.CertificationType == models.SignedCertificationTypeCloseoutReviewedPPMPAYMENT {
sswDate := FormatDate(cert.UpdatedAt) // We use updatedat to get the most recent signature dates
return sswDate
}
}
}
}
return "Uncertified"
Copy link
Contributor

@MInthavongsay MInthavongsay Aug 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 418 and 434: I'm assuming this is a worse case scenario? It should never get to this, right? You may want to throw an error and/or log here.

In addition, you want to create a generic method in place of formatAOADate/formatSSWDate methods that takes in the cert array, uuid and enum. It should return null if nothing matches. From there you can log or throw an error in the case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

418 is what we discussed in the standup this morning. Right now, there's a way to download the AOA before the SC submits move details where their signature is added. Until we can revisit the aoa download availability, our PO just wants it to use the date the PDF generation was ran.

As for 434, this should never occur, so an error may be more appropriate here. If that's what we want to go with, I'll add it to the todo list for later today.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've gone ahead and added the error handling to this method. Now, if there's ever an uncertified payment packet downloaded before it should be, it will error out. Honestly, making this sort of thing easier in this service was long overdue, so I think that's a great change.

As for the generic method idea, I'm going to push back against that because that's the solution I attempted initially. Due to the implementation in the way that the service determines which packet is being generated, as well as the best way for handling adding the dates themselves from the certs, and the two alternate solutions to when a date shouldn't be used... I believe the current solution is easier to read, easier to maintain, is marginally better performance-wise, and uses about the same amount of code as combining them.

I do realize that the amount of methods in this service is... kind of a lot, so I'd see why we'd want to cut down on them. But if I need more justification still, then an additional reason for adding 2 in this story is that these changes also removed a near-duplicate method in formatSignatureDate.

Just let me know if any objections.

}

// FormatLocation formats AuthorizedOrigin and AuthorizedDestination for Shipment Summary Worksheet
Expand Down Expand Up @@ -873,7 +903,7 @@ func (SSWPPMGenerator *SSWPPMGenerator) FillSSWPDFForm(Page1Values services.Page
var sswHeader = header{
Source: "SSWPDFTemplate.pdf",
Version: "pdfcpu v0.8.0 dev",
Creation: "2024-07-19 18:07:42 UTC",
Creation: "2024-08-21 19:31:01 UTC",
Producer: "macOS Version 13.5 (Build 22G74) Quartz PDFContext, AppendMode 1.1",
}

Expand Down Expand Up @@ -936,7 +966,7 @@ func createTextFields(data interface{}, pages ...int) []textField {
ID: fmt.Sprintf("%d", len(textFields)+1),
Name: field.Name,
Value: fmt.Sprintf("%v", value),
Multiline: false,
Multiline: true,
Locked: false,
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,8 +336,6 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatValuesShipmentSumma
}
sswPage1 := FormatValuesShipmentSummaryWorksheetFormPage1(ssd, false)

suite.Equal("01-Jan-2019", sswPage1.PreparationDate)

suite.Equal("Jenkins Jr., Marcus Joseph", sswPage1.ServiceMemberName)
suite.Equal("E-9", sswPage1.RankGrade)
suite.Equal("Air Force", sswPage1.ServiceBranch)
Expand Down Expand Up @@ -654,17 +652,24 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatPPMWeightFinal() {
suite.Equal("1,000 lbs - Actual", FormatPPMWeightFinal(pounds))
}

func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSignedCertifications() {
func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatAOASignedCertifications() {
move := factory.BuildMoveWithPPMShipment(suite.DB(), nil, nil)
testDate := time.Now()
certifications := Certifications{
testDate := time.Now() // due to using updatedAt, time.Now() needs to be used to test cert times and dates
aoaCertifications := Certifications{
CustomerField: "",
OfficeField: "AOA: Firstname Lastname\nSSW: ",
DateField: "AOA: " + FormatSignatureDate(testDate) + "\nSSW: ",
DateField: "AOA: " + FormatDate(testDate) + "\nSSW: ",
}
sswCertifications := Certifications{
CustomerField: "",
OfficeField: "AOA: Firstname Lastname\nSSW: Firstname Lastname",
DateField: "AOA: " + FormatDate(testDate) + "\nSSW: " + FormatDate(testDate),
}
prepAOADate := FormatDate(testDate)
prepSSWDate := FormatDate(testDate)

signedCertType := models.SignedCertificationTypePreCloseoutReviewedPPMPAYMENT
ppmPaymentsignedCertification := factory.BuildSignedCertification(suite.DB(), []factory.Customization{
aoaSignedCertification := factory.BuildSignedCertification(suite.DB(), []factory.Customization{
{
Model: move,
LinkOnly: true,
Expand All @@ -680,23 +685,73 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSignedCertification
},
}, nil)
var certs []*models.SignedCertification
certs = append(certs, &ppmPaymentsignedCertification)
certs = append(certs, &aoaSignedCertification)

formattedSignature := formatSignedCertifications(certs, move.MTOShipments[0].PPMShipment.ID)
formattedDate := formatAOADate(certs, move.MTOShipments[0].PPMShipment.ID)
suite.Equal(prepAOADate, formattedDate)
suite.Equal(aoaCertifications, formattedSignature)

suite.Equal(certifications, formattedSignature)
}
signedCertType = models.SignedCertificationTypeCloseoutReviewedPPMPAYMENT
ppmPaymentsignedCertification := factory.BuildSignedCertification(suite.DB(), []factory.Customization{
{
Model: move,
LinkOnly: true,
},
{
Model: models.SignedCertification{
CertificationType: &signedCertType,
CertificationText: "APPROVED",
Signature: "Firstname Lastname",
UpdatedAt: testDate,
PpmID: models.UUIDPointer(move.MTOShipments[0].PPMShipment.ID),
},
},
}, nil)
certs = append(certs, &ppmPaymentsignedCertification)

formattedSignature = formatSignedCertifications(certs, move.MTOShipments[0].PPMShipment.ID)
formattedDate = formatSSWDate(certs, move.MTOShipments[0].PPMShipment.ID)
suite.Equal(prepSSWDate, formattedDate)
suite.Equal(sswCertifications, formattedSignature)

func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSignatureDate() {
signatureDate := time.Date(2019, time.January, 26, 14, 40, 0, 0, time.UTC)
}

signature := models.SignedCertification{
Date: signatureDate,
func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatSSWSignedCertifications() {
move := factory.BuildMoveWithPPMShipment(suite.DB(), nil, nil)
testDate := time.Now() // due to using updatedAt, time.Now() needs to be used to test cert times and dates
sswCertifications := Certifications{
CustomerField: "",
OfficeField: "AOA: \nSSW: Firstname Lastname",
DateField: "AOA: " + "\nSSW: " + FormatDate(testDate),
}
prepSSWDate := FormatDate(testDate)

formattedDate := FormatSignatureDate(signature.Date)
var certs []*models.SignedCertification

signedCertType := models.SignedCertificationTypeCloseoutReviewedPPMPAYMENT
ppmPaymentsignedCertification := factory.BuildSignedCertification(suite.DB(), []factory.Customization{
{
Model: move,
LinkOnly: true,
},
{
Model: models.SignedCertification{
CertificationType: &signedCertType,
CertificationText: "APPROVED",
Signature: "Firstname Lastname",
UpdatedAt: testDate,
PpmID: models.UUIDPointer(move.MTOShipments[0].PPMShipment.ID),
},
},
}, nil)
certs = append(certs, &ppmPaymentsignedCertification)

formattedSignature := formatSignedCertifications(certs, move.MTOShipments[0].PPMShipment.ID)
formattedDate := formatSSWDate(certs, move.MTOShipments[0].PPMShipment.ID)
suite.Equal(prepSSWDate, formattedDate)
suite.Equal(sswCertifications, formattedSignature)

suite.Equal("26 Jan 2019", formattedDate)
}

func (suite *ShipmentSummaryWorksheetServiceSuite) TestFormatAddress() {
Expand Down Expand Up @@ -788,9 +843,9 @@ func (suite *ShipmentSummaryWorksheetServiceSuite) TestCreateTextFields() {
result := createTextFields(testData, pages...)

expectedResult := []textField{
{Pages: pages, ID: "1", Name: "Field1", Value: "Value1", Multiline: false, Locked: false},
{Pages: pages, ID: "2", Name: "Field2", Value: "42", Multiline: false, Locked: false},
{Pages: pages, ID: "3", Name: "Field3", Value: "true", Multiline: false, Locked: false},
{Pages: pages, ID: "1", Name: "Field1", Value: "Value1", Multiline: true, Locked: false},
{Pages: pages, ID: "2", Name: "Field2", Value: "42", Multiline: true, Locked: false},
{Pages: pages, ID: "3", Name: "Field3", Value: "true", Multiline: true, Locked: false},
}

suite.Equal(result, expectedResult)
Expand Down