From f5795db6ed16da42fb3e71978fa00feb396aa9ec Mon Sep 17 00:00:00 2001 From: Daniel Covington Date: Wed, 1 Apr 2026 12:37:46 -0400 Subject: [PATCH] Add purple envelope delivery paperwork generation --- App/Controllers/Kit/KitController.asp | 218 +++++++++- App/DomainModels/KitRepository.asp | 31 ++ App/ViewModels/KitViewModels.asp | 10 + .../Kit/SwitchBoardPurpleEnvelopeEdit.asp | 5 + App/app.config.asp | 4 +- Data/Delivery_Labels.rep | 275 ++++++++++++ Data/Delivery_PackingSlip.rep | 331 +++++++++++++++ ...tion_21_Create_DeliveryLabelPage_Table.asp | 24 ++ Data/Migrations/migrate.asp | 1 + Tests/TestCase_PurpleEnvelopeReport.asp | 117 ++++- ...rint-delivery-paperwork-purple-envelope.md | 401 ++++++++++++++++++ 11 files changed, 1413 insertions(+), 4 deletions(-) create mode 100644 Data/Delivery_Labels.rep create mode 100644 Data/Delivery_PackingSlip.rep create mode 100644 Data/Migrations/Migration_21_Create_DeliveryLabelPage_Table.asp create mode 100644 _bmad-output/implementation-artifacts/tech-spec-print-delivery-paperwork-purple-envelope.md diff --git a/App/Controllers/Kit/KitController.asp b/App/Controllers/Kit/KitController.asp index 561086e..1dc22bd 100644 --- a/App/Controllers/Kit/KitController.asp +++ b/App/Controllers/Kit/KitController.asp @@ -98,7 +98,8 @@ Class KitController End Sub Public Sub SwitchBoardPurpleEnvelopeEdit - + Flash.ShowErrorsIfPresent + Flash.ShowSuccessIfPresent dim id : id = Request.QueryString("Id") set Model = new SwitchBoard_ViewModel_Class set Model.Kit = KitRepository.SwitchBoardPurpleEnvelopeEditFindById(id) @@ -119,6 +120,221 @@ Class KitController %> <% End Sub + Public Sub PrintDeliveryPaperwork + dim id : id = Trim(Request.QueryString("Id") & "") + If Len(id) = 0 Or Not IsNumeric(id) Then + Flash.AddError "Unable to print delivery paperwork because the kit ID is missing or invalid." + MVC.RedirectToAction "SwitchBoardPurpleEnvelopsIndex" + Exit Sub + End If + + dim kitId : kitId = CLng(id) + dim kitInfo + On Error Resume Next + set kitInfo = KitRepository.SwitchBoardPurpleEnvelopeEditFindById(kitId) + If Err.Number <> 0 Then + dim loadErrDescription : loadErrDescription = Err.Description + Err.Clear + On Error GoTo 0 + Flash.AddError "Unable to load the requested kit. " & loadErrDescription + MVC.RedirectToAction "SwitchBoardPurpleEnvelopsIndex" + Exit Sub + End If + On Error GoTo 0 + + If UCase(Trim(kitInfo.Status & "")) <> "DONE" Then + Flash.AddError "Delivery paperwork can only be printed when the kit status is Done." + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + + dim inkjetCount : inkjetCount = 0 + If IsNumeric(kitInfo.LabelCount) Then + inkjetCount = CLng(kitInfo.LabelCount) + End If + If inkjetCount <= 0 Then + Flash.AddError "Delivery paperwork cannot be generated because no labels were found for this kit." + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + + dim appPath : appPath = Request.ServerVariables("APPL_PHYSICAL_PATH") + dim labelsTemplatePath : labelsTemplatePath = appPath & "Data\Delivery_Labels.rep" + dim packingSlipTemplatePath : packingSlipTemplatePath = appPath & "Data\Delivery_PackingSlip.rep" + If FSO.FileExists(labelsTemplatePath) = False Then + Flash.AddError "Delivery labels template was not found: Data\Delivery_Labels.rep" + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + If FSO.FileExists(packingSlipTemplatePath) = False Then + Flash.AddError "Delivery packing slip template was not found: Data\Delivery_PackingSlip.rep" + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + + dim totalBoxes : totalBoxes = Int((inkjetCount + 1099) / 1100) + If totalBoxes <= 0 Then + Flash.AddError "Delivery paperwork cannot be generated because no shipping boxes were calculated." + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + + dim jurisdiction : jurisdiction = Trim(kitInfo.Jurisdiction & "") + dim jCode : jCode = Trim(kitInfo.JCode & "") + dim jobNumber : jobNumber = Trim(kitInfo.JobNumber & "") + + dim pages : set pages = new LinkedList_Class + dim boxNum : boxNum = totalBoxes + dim pageNum : pageNum = 1 + dim page, slot, labelText + + Do While boxNum >= 1 + set page = new DeliveryLabelPage_Class + page.PageNum = pageNum + For slot = 1 To 6 + labelText = "" + If boxNum >= 1 Then + labelText = jurisdiction & vbCrLf & _ + "Box " & boxNum & " of " & totalBoxes & vbCrLf & _ + "Total Printed " & inkjetCount & vbCrLf & _ + "Job# " & jobNumber + boxNum = boxNum - 1 + End If + + Select Case slot + Case 1 : page.Label1 = labelText + Case 2 : page.Label2 = labelText + Case 3 : page.Label3 = labelText + Case 4 : page.Label4 = labelText + Case 5 : page.Label5 = labelText + Case 6 : page.Label6 = labelText + End Select + Next + + pages.Push page + pageNum = pageNum + 1 + Loop + + dim exportFolder : exportFolder = ExportDirectory & jCode & "-" & jurisdiction + On Error Resume Next + If FSO.FolderExists(exportFolder) = False Then + FSO.CreateFolder(exportFolder) + End If + If Err.Number <> 0 Then + dim folderErrDescription : folderErrDescription = Err.Description + Err.Clear + On Error GoTo 0 + Flash.AddError "Unable to create the delivery export folder. " & folderErrDescription + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + + dim labelsFilename : labelsFilename = jCode & "-" & jurisdiction & "_delivery_labels.pdf" + dim packingSlipFilename : packingSlipFilename = jCode & "-" & jurisdiction & "_delivery_packing_slip.pdf" + dim labelsFinalPath : labelsFinalPath = exportFolder & "\" & labelsFilename + dim packingSlipFinalPath : packingSlipFinalPath = exportFolder & "\" & packingSlipFilename + dim labelsTempPath : labelsTempPath = appPath & "Data\" & jCode & "-" & jurisdiction & "_delivery_labels.tmp.pdf" + dim packingSlipTempPath : packingSlipTempPath = appPath & "Data\" & jCode & "-" & jurisdiction & "_delivery_packing_slip.tmp.pdf" + + If FSO.FileExists(labelsTempPath) Then FSO.DeleteFile labelsTempPath + If FSO.FileExists(packingSlipTempPath) Then FSO.DeleteFile packingSlipTempPath + If Err.Number <> 0 Then + dim tempErrDescription : tempErrDescription = Err.Description + Err.Clear + On Error GoTo 0 + Flash.AddError "Unable to clear previous temporary delivery paperwork files. " & tempErrDescription + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + On Error GoTo 0 + + dim databaseConnectionString + If dev = true Then + databaseConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Engine Type=5;Data Source=" & appPath & "Data\webdata - Copy.mdb;" + Else + databaseConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Engine Type=5;Data Source=C:\inetpub\Data\webdata - Copy.mdb;" + End If + + On Error Resume Next + KitRepository.SaveDeliveryLabelPages kitId, pages + If Err.Number <> 0 Then + dim stageErrDescription : stageErrDescription = Err.Description + Err.Clear + On Error GoTo 0 + Flash.AddError "Unable to stage delivery label data. " & stageErrDescription + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + + dim labelsReport : set labelsReport = Server.CreateObject("ReportMan.ReportManX") + labelsReport.Filename = labelsTemplatePath + labelsReport.SetDatabaseConnectionString "TRACKINGKITLABELS", databaseConnectionString + labelsReport.Preview = False + labelsReport.ShowProgress = False + labelsReport.ShowPrintDialog = False + labelsReport.SetParamValue "PBKITID", CInt(kitId) + labelsReport.SaveToPdf labelsTempPath, 1 + set labelsReport = Nothing + + If Err.Number <> 0 Then + dim labelsErrDescription : labelsErrDescription = Err.Description + Err.Clear + On Error GoTo 0 + Flash.AddError "Unable to generate the delivery labels PDF. " & labelsErrDescription + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + + dim slipReport : set slipReport = Server.CreateObject("ReportMan.ReportManX") + slipReport.Filename = packingSlipTemplatePath + slipReport.SetDatabaseConnectionString "TRACKINGKITLABELS", databaseConnectionString + slipReport.Preview = False + slipReport.ShowProgress = False + slipReport.ShowPrintDialog = False + slipReport.SetParamValue "PBKITID", CInt(kitId) + slipReport.SaveToPdf packingSlipTempPath, 1 + set slipReport = Nothing + + If Err.Number <> 0 Then + dim slipErrDescription : slipErrDescription = Err.Description + Err.Clear + On Error GoTo 0 + Flash.AddError "Unable to generate the delivery packing slip PDF. " & slipErrDescription + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + + If FSO.FileExists(labelsFinalPath) Then FSO.DeleteFile labelsFinalPath + If FSO.FileExists(packingSlipFinalPath) Then FSO.DeleteFile packingSlipFinalPath + FSO.MoveFile labelsTempPath, labelsFinalPath + FSO.MoveFile packingSlipTempPath, packingSlipFinalPath + + If Err.Number <> 0 Then + dim moveErrDescription : moveErrDescription = Err.Description + Err.Clear + On Error GoTo 0 + Flash.AddError "Delivery paperwork PDFs were generated but could not be moved into the export folder. " & moveErrDescription + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + + On Error GoTo 0 + On Error Resume Next + KitRepository.DeleteDeliveryLabelPages kitId + If Err.Number <> 0 Then + dim cleanupErrDescription : cleanupErrDescription = Err.Description + Err.Clear + On Error GoTo 0 + Flash.AddError "Delivery paperwork PDFs were generated, but staging cleanup failed. " & cleanupErrDescription + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + Exit Sub + End If + On Error GoTo 0 + + Flash.Success = "Delivery paperwork generated successfully." + MVC.RedirectToActionExt "SwitchBoardPurpleEnvelopeEdit", Array("Id", kitId) + End Sub + Public Sub Index dim page_size : page_size = 10 diff --git a/App/DomainModels/KitRepository.asp b/App/DomainModels/KitRepository.asp index c2e1448..169f50c 100644 --- a/App/DomainModels/KitRepository.asp +++ b/App/DomainModels/KitRepository.asp @@ -354,6 +354,37 @@ Public Sub Update(model) model.ID) End Sub + Public Sub SaveDeliveryLabelPages(ByVal kitId, ByVal pages) + dim sql, pageIt, page + sql = "INSERT INTO [DeliveryLabelPage] ([KitID],[PageNum],[Label1],[Label2],[Label3],[Label4],[Label5],[Label6]) VALUES (?,?,?,?,?,?,?,?)" + + DAL.BeginTransaction + On Error Resume Next + + DAL.Execute "DELETE FROM [DeliveryLabelPage] WHERE [KitID] = ?", CLng(kitId) + + set pageIt = pages.Iterator + Do While Err.Number = 0 And pageIt.HasNext + set page = pageIt.GetNext() + DAL.Execute sql, Array(CLng(kitId), CInt(page.PageNum), page.Label1, page.Label2, page.Label3, page.Label4, page.Label5, page.Label6) + Loop + + If Err.Number <> 0 Then + dim errNumber : errNumber = Err.Number + dim errDescription : errDescription = Err.Description + DAL.RollbackTransaction + On Error GoTo 0 + Err.Raise errNumber, "KitRepository_Class.SaveDeliveryLabelPages", errDescription + End If + + On Error GoTo 0 + DAL.CommitTransaction + End Sub + + Public Sub DeleteDeliveryLabelPages(ByVal kitId) + DAL.Execute "DELETE FROM [DeliveryLabelPage] WHERE [KitID] = ?", CLng(kitId) + End Sub + Public Sub Delete(id) dim sql : sql = "DELETE FROM [Kit] WHERE [ID] = ?" DAL.Execute sql, id diff --git a/App/ViewModels/KitViewModels.asp b/App/ViewModels/KitViewModels.asp index 9022437..3081c03 100644 --- a/App/ViewModels/KitViewModels.asp +++ b/App/ViewModels/KitViewModels.asp @@ -49,6 +49,16 @@ Class SwitchBoard_PurpleEnvelopesViewModel_Class Public RecordCount End Class +Class DeliveryLabelPage_Class + Public PageNum + Public Label1 + Public Label2 + Public Label3 + Public Label4 + Public Label5 + Public Label6 +End Class + Class Create_ViewModel_Class Public Title Public JobNumber diff --git a/App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp b/App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp index 880e222..6a76b9d 100644 --- a/App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp +++ b/App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp @@ -95,6 +95,11 @@

Created On: <%= Model.Kit.CreatedOn %>

Labels Printed On : <%= Model.Kit.LabelsPrinted %>

Exported to SnailWorks On: <%= Model.Kit.ExportedToSnailWorks %>

+ <% If Model.Kit.Status = "Done" Then %> +

+ <%= HTML.LinkToExt(" Print Delivery Paperwork", "Kit", "PrintDeliveryPaperwork", Array("Id", Model.Kit.ID), Array("class", "btn btn-primary")) %> +

+ <% End If %> <% IF Model.Kit.Status = "Ready To Assign STIDS" THEN %> <%= HTML.FormTag("Kit","SwitchBoardPurpleEnvelopeEditPost",empty,empty) %> <% diff --git a/App/app.config.asp b/App/app.config.asp index c7c877a..d343223 100644 --- a/App/app.config.asp +++ b/App/app.config.asp @@ -11,9 +11,9 @@ dev = true dim ExportDirectory select case dev case "local" - ExportDirectory = "D:\Development\Tracking_Kits\uploads" + ExportDirectory = "D:\Development\Tracking_Kits\uploads\" case true - ExportDirectory = "D:\Development\Tracking_Kits\uploads" + ExportDirectory = "D:\Development\Tracking_Kits\uploads\" case else ExportDirectory ="\\kci-syn-cl01\PC Transfer\TrackingDataExport\" diff --git a/Data/Delivery_Labels.rep b/Data/Delivery_Labels.rep new file mode 100644 index 0000000..a8bb7e4 --- /dev/null +++ b/Data/Delivery_Labels.rep @@ -0,0 +1,275 @@ +object TRpReport + Pagesize = rpPageSizeCustom + PageHeight = 7920 + PageWidth = 12240 + PageBackColor = 16777215 + LeftMargin = 180 + TopMargin = 180 + RightMargin = 180 + BottomMargin = 180 + SubReports = < + item + SubReport = TRpSubReport0 + end> + DataInfo = < + item + Alias = 'DELIVERYLABELS' + DatabaseAlias = 'TRACKINGKITLABELS' + SQL = + 'SELECT Label1, Label2, Label3, Label4, Label5, Label6 FROM Deliv' + + 'eryLabelPage WHERE KitID = PBKITID ORDER BY PageNum' + end> + DatabaseInfo = < + item + Alias = 'TRACKINGKITLABELS' + LoadParams = True + LoadDriverParams = True + LoginPrompt = False + Driver = rpdataado + ReportTable = 'REPMAN_REPORTS' + ReportSearchField = 'REPORT_NAME' + ReportField = 'REPORT' + ReportGroupsTable = 'REPMAN_GROUPS' + ADOConnectionString = + 'Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Engine Type=5;Data So' + + 'urce=C:\inetpub\Data\webdata - Copy.mdb;Persist Security Info=Fa' + + 'lse;' + end> + Params = < + item + Name = 'PBKITID' + AllowNulls = False + Value = '1' + ParamType = rpParamInteger + Datasets.Strings = ( + 'DELIVERYLABELS') + SearchDataset = 'DELIVERYLABELS' + SearchParam = 'PBKITID' + Description = '' + Hint = '' + Search = '' + ErrorMessage = '' + Validation = '' + end> + StreamFormat = rpStreamText + ReportAction = [] + Type1Font = poHelvetica + WFontName = 'Arial' + LFontName = 'Helvetica' + object TRpSubReport0: TRpSubReport + Sections = < + item + Section = TRpSection0 + end> + Alias = 'DELIVERYLABELS' + end + object TRpSection0: TRpSection + Width = 12240 + Height = 7920 + SubReport = TRpSubReport0 + ChangeBool = False + PageRepeat = False + SkipPage = False + AlignBottom = False + SectionType = rpsecdetail + Components = < + item + Component = TRpLabel0 + end + item + Component = TRpExpression1 + end + item + Component = TRpExpression2 + end + item + Component = TRpExpression3 + end + item + Component = TRpExpression4 + end + item + Component = TRpExpression5 + end + item + Component = TRpExpression6 + end> + ExternalTable = 'REPMAN_REPORTS' + ExternalField = 'REPORT' + ExternalSearchField = 'REPORT_NAME' + StreamFormat = rpStreamText + BackStyle = baPrint + DrawStyle = rpDrawStretch + CachedImage = rpCachedFixed + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + ChangeExpression = '' + BeginPageExpression = '' + ChangeExpression = '' + SkipExpreV = '' + SkipExpreH = '' + SkipToPageExpre = '' + BackExpression = '' + Stream = {0000000000000000} + end + object TRpLabel0: TRpLabel + Width = 3600 + Height = 240 + PosX = 300 + PosY = 180 + Type1Font = poHelvetica + FontSize = 9 + FontStyle = 1 + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + WideText = 'Delivery Labels' + end + object TRpExpression1: TRpExpression + Width = 5400 + Height = 1800 + PosX = 360 + PosY = 720 + Type1Font = poHelvetica + FontSize = 11 + Alignment = 0 + AutoExpand = True + AutoContract = False + ExportPosition = 0 + ExportSize = 1 + ExportDoNewLine = False + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + Expression = 'DELIVERYLABELS.Label1' + DisplayFormat = '' + ExportDisplayFormat = '' + AgIniValue = '0' + ExportExpression = '' + end + object TRpExpression2: TRpExpression + Width = 5400 + Height = 1800 + PosX = 6480 + PosY = 720 + Type1Font = poHelvetica + FontSize = 11 + Alignment = 0 + AutoExpand = True + AutoContract = False + ExportPosition = 0 + ExportSize = 1 + ExportDoNewLine = False + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + Expression = 'DELIVERYLABELS.Label2' + DisplayFormat = '' + ExportDisplayFormat = '' + AgIniValue = '0' + ExportExpression = '' + end + object TRpExpression3: TRpExpression + Width = 5400 + Height = 1800 + PosX = 360 + PosY = 2880 + Type1Font = poHelvetica + FontSize = 11 + Alignment = 0 + AutoExpand = True + AutoContract = False + ExportPosition = 0 + ExportSize = 1 + ExportDoNewLine = False + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + Expression = 'DELIVERYLABELS.Label3' + DisplayFormat = '' + ExportDisplayFormat = '' + AgIniValue = '0' + ExportExpression = '' + end + object TRpExpression4: TRpExpression + Width = 5400 + Height = 1800 + PosX = 6480 + PosY = 2880 + Type1Font = poHelvetica + FontSize = 11 + Alignment = 0 + AutoExpand = True + AutoContract = False + ExportPosition = 0 + ExportSize = 1 + ExportDoNewLine = False + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + Expression = 'DELIVERYLABELS.Label4' + DisplayFormat = '' + ExportDisplayFormat = '' + AgIniValue = '0' + ExportExpression = '' + end + object TRpExpression5: TRpExpression + Width = 5400 + Height = 1800 + PosX = 360 + PosY = 5040 + Type1Font = poHelvetica + FontSize = 11 + Alignment = 0 + AutoExpand = True + AutoContract = False + ExportPosition = 0 + ExportSize = 1 + ExportDoNewLine = False + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + Expression = 'DELIVERYLABELS.Label5' + DisplayFormat = '' + ExportDisplayFormat = '' + AgIniValue = '0' + ExportExpression = '' + end + object TRpExpression6: TRpExpression + Width = 5400 + Height = 1800 + PosX = 6480 + PosY = 5040 + Type1Font = poHelvetica + FontSize = 11 + Alignment = 0 + AutoExpand = True + AutoContract = False + ExportPosition = 0 + ExportSize = 1 + ExportDoNewLine = False + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + Expression = 'DELIVERYLABELS.Label6' + DisplayFormat = '' + ExportDisplayFormat = '' + AgIniValue = '0' + ExportExpression = '' + end +end diff --git a/Data/Delivery_PackingSlip.rep b/Data/Delivery_PackingSlip.rep new file mode 100644 index 0000000..66e9c2a --- /dev/null +++ b/Data/Delivery_PackingSlip.rep @@ -0,0 +1,331 @@ +object TRpReport + Pagesize = rpPageSizeCustom + PageHeight = 7920 + PageWidth = 12240 + PageBackColor = 16777215 + LeftMargin = 360 + TopMargin = 360 + RightMargin = 360 + BottomMargin = 360 + SubReports = < + item + SubReport = TRpSubReport0 + end> + DataInfo = < + item + Alias = 'PACKINGSLIP' + DatabaseAlias = 'TRACKINGKITLABELS' + SQL = + 'SELECT Kit.JobNumber AS JobNumber, Jurisdiction.Name AS Jurisdic' + + 'tion, (SELECT COUNT(*) FROM InkjetRecords WHERE KitID = Kit.ID) A' + + 'S TotalPrinted, Int(((SELECT COUNT(*) FROM InkjetRecords WHERE Kit' + + 'ID = Kit.ID) + 1099) / 1100) AS TotalBoxes FROM Kit INNER JOIN Ju' + + 'risdiction ON Kit.JCode = Jurisdiction.JCode WHERE Kit.ID = PBKITI' + + 'D' + end> + DatabaseInfo = < + item + Alias = 'TRACKINGKITLABELS' + LoadParams = True + LoadDriverParams = True + LoginPrompt = False + Driver = rpdataado + ReportTable = 'REPMAN_REPORTS' + ReportSearchField = 'REPORT_NAME' + ReportField = 'REPORT' + ReportGroupsTable = 'REPMAN_GROUPS' + ADOConnectionString = + 'Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Engine Type=5;Data So' + + 'urce=C:\inetpub\Data\webdata - Copy.mdb;Persist Security Info=Fa' + + 'lse;' + end> + Params = < + item + Name = 'PBKITID' + AllowNulls = False + Value = '1' + ParamType = rpParamInteger + Datasets.Strings = ( + 'PACKINGSLIP') + SearchDataset = 'PACKINGSLIP' + SearchParam = 'PBKITID' + Description = '' + Hint = '' + Search = '' + ErrorMessage = '' + Validation = '' + end> + StreamFormat = rpStreamText + ReportAction = [] + Type1Font = poHelvetica + WFontName = 'Arial' + LFontName = 'Helvetica' + object TRpSubReport0: TRpSubReport + Sections = < + item + Section = TRpSection0 + end> + Alias = 'PACKINGSLIP' + end + object TRpSection0: TRpSection + Width = 12240 + Height = 7200 + SubReport = TRpSubReport0 + ChangeBool = False + PageRepeat = False + SkipPage = False + AlignBottom = False + SectionType = rpsecdetail + Components = < + item + Component = TRpLabel0 + end + item + Component = TRpLabel1 + end + item + Component = TRpExpression1 + end + item + Component = TRpLabel2 + end + item + Component = TRpExpression2 + end + item + Component = TRpLabel3 + end + item + Component = TRpExpression3 + end + item + Component = TRpLabel4 + end + item + Component = TRpExpression4 + end + item + Component = TRpLabel5 + end + item + Component = TRpLabel6 + end> + ExternalTable = 'REPMAN_REPORTS' + ExternalField = 'REPORT' + ExternalSearchField = 'REPORT_NAME' + StreamFormat = rpStreamText + BackStyle = baPrint + DrawStyle = rpDrawStretch + CachedImage = rpCachedFixed + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + ChangeExpression = '' + BeginPageExpression = '' + ChangeExpression = '' + SkipExpreV = '' + SkipExpreH = '' + SkipToPageExpre = '' + BackExpression = '' + Stream = {0000000000000000} + end + object TRpLabel0: TRpLabel + Width = 3600 + Height = 360 + PosX = 360 + PosY = 360 + Type1Font = poHelvetica + FontSize = 14 + FontStyle = 1 + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + WideText = 'Delivery Packing Slip' + end + object TRpLabel1: TRpLabel + Width = 2280 + Height = 240 + PosX = 360 + PosY = 1260 + Type1Font = poHelvetica + FontSize = 11 + FontStyle = 1 + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + WideText = 'Jurisdiction:' + end + object TRpExpression1: TRpExpression + Width = 5400 + Height = 240 + PosX = 3000 + PosY = 1260 + Type1Font = poHelvetica + FontSize = 11 + Alignment = 0 + AutoExpand = False + AutoContract = False + ExportPosition = 0 + ExportSize = 1 + ExportDoNewLine = False + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + Expression = 'PACKINGSLIP.Jurisdiction' + DisplayFormat = '' + ExportDisplayFormat = '' + AgIniValue = '0' + ExportExpression = '' + end + object TRpLabel2: TRpLabel + Width = 2280 + Height = 240 + PosX = 360 + PosY = 1800 + Type1Font = poHelvetica + FontSize = 11 + FontStyle = 1 + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + WideText = 'Job Number:' + end + object TRpExpression2: TRpExpression + Width = 5400 + Height = 240 + PosX = 3000 + PosY = 1800 + Type1Font = poHelvetica + FontSize = 11 + Alignment = 0 + AutoExpand = False + AutoContract = False + ExportPosition = 0 + ExportSize = 1 + ExportDoNewLine = False + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + Expression = 'PACKINGSLIP.JobNumber' + DisplayFormat = '' + ExportDisplayFormat = '' + AgIniValue = '0' + ExportExpression = '' + end + object TRpLabel3: TRpLabel + Width = 2280 + Height = 240 + PosX = 360 + PosY = 2340 + Type1Font = poHelvetica + FontSize = 11 + FontStyle = 1 + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + WideText = 'Total Printed:' + end + object TRpExpression3: TRpExpression + Width = 5400 + Height = 240 + PosX = 3000 + PosY = 2340 + Type1Font = poHelvetica + FontSize = 11 + Alignment = 0 + AutoExpand = False + AutoContract = False + ExportPosition = 0 + ExportSize = 1 + ExportDoNewLine = False + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + Expression = 'PACKINGSLIP.TotalPrinted' + DisplayFormat = '' + ExportDisplayFormat = '' + AgIniValue = '0' + ExportExpression = '' + end + object TRpLabel4: TRpLabel + Width = 2280 + Height = 240 + PosX = 360 + PosY = 2880 + Type1Font = poHelvetica + FontSize = 11 + FontStyle = 1 + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + WideText = 'Total Boxes:' + end + object TRpExpression4: TRpExpression + Width = 5400 + Height = 240 + PosX = 3000 + PosY = 2880 + Type1Font = poHelvetica + FontSize = 11 + Alignment = 0 + AutoExpand = False + AutoContract = False + ExportPosition = 0 + ExportSize = 1 + ExportDoNewLine = False + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + Expression = 'PACKINGSLIP.TotalBoxes' + DisplayFormat = '' + ExportDisplayFormat = '' + AgIniValue = '0' + ExportExpression = '' + end + object TRpLabel5: TRpLabel + Width = 2880 + Height = 240 + PosX = 360 + PosY = 4320 + Type1Font = poHelvetica + FontSize = 11 + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + WideText = 'Received By: ____________________' + end + object TRpLabel6: TRpLabel + Width = 2880 + Height = 240 + PosX = 360 + PosY = 5040 + Type1Font = poHelvetica + FontSize = 11 + PrintCondition = '' + DoBeforePrint = '' + DoAfterPrint = '' + WFontName = 'Arial' + LFontName = 'Helvetica' + WideText = 'Date: __________________________' + end +end diff --git a/Data/Migrations/Migration_21_Create_DeliveryLabelPage_Table.asp b/Data/Migrations/Migration_21_Create_DeliveryLabelPage_Table.asp new file mode 100644 index 0000000..490cdc2 --- /dev/null +++ b/Data/Migrations/Migration_21_Create_DeliveryLabelPage_Table.asp @@ -0,0 +1,24 @@ +<% +Class Migration_21_Create_DeliveryLabelPage_Table + Public Migration + + Public Sub Up + Migration.Do "CREATE TABLE [DeliveryLabelPage] (" & _ + "[ID] COUNTER CONSTRAINT [PrimaryKey] PRIMARY KEY, " & _ + "[KitID] LONG NOT NULL, " & _ + "[PageNum] INTEGER NOT NULL, " & _ + "[Label1] MEMO, " & _ + "[Label2] MEMO, " & _ + "[Label3] MEMO, " & _ + "[Label4] MEMO, " & _ + "[Label5] MEMO, " & _ + "[Label6] MEMO);" + End Sub + + Public Sub Down + Migration.Do "DROP TABLE [DeliveryLabelPage];" + End Sub +End Class + +Migrations.Add "Migration_21_Create_DeliveryLabelPage_Table" +%> diff --git a/Data/Migrations/migrate.asp b/Data/Migrations/migrate.asp index 3c67c90..9629f25 100644 --- a/Data/Migrations/migrate.asp +++ b/Data/Migrations/migrate.asp @@ -48,6 +48,7 @@ Migrations.Tracing = false + <% Sub HandleMigration putl "Starting Version: " & Migrations.Version & "" diff --git a/Tests/TestCase_PurpleEnvelopeReport.asp b/Tests/TestCase_PurpleEnvelopeReport.asp index 9512d0a..6533314 100644 --- a/Tests/TestCase_PurpleEnvelopeReport.asp +++ b/Tests/TestCase_PurpleEnvelopeReport.asp @@ -11,6 +11,7 @@ Class PurpleEnvelopeReport_Tests Public Sub Setup m_tempDbPath = CreateDisposableDatabaseCopy() UseDisposableDatabase m_tempDbPath + EnsureDeliveryLabelPageTable End Sub Public Sub Teardown @@ -27,9 +28,14 @@ Class PurpleEnvelopeReport_Tests "Test_UpdateColorForKit_Updates_All_Seeded_Rows_For_The_Target_Kit", _ "Test_UpdateColorForPrecinct_Updates_Only_The_Targeted_Precinct", _ "Test_SwitchBoardPurpleEnvelopeEditFindById_Returns_Seeded_Header_Data", _ + "Test_SaveDeliveryLabelPages_Persists_Seeded_Page_Data", _ + "Test_SaveDeliveryLabelPages_Replaces_Existing_Pages_For_The_Kit", _ + "Test_DeleteDeliveryLabelPages_Removes_All_Staged_Pages_For_The_Kit", _ "Test_KitController_Post_Actions_Still_Delegate_To_Color_Update_Repositories", _ "Test_Report_View_Keeps_Print_Only_CSS_Contract", _ - "Test_Report_View_Keeps_No_Data_Row_And_Page_Spacer" _ + "Test_Report_View_Keeps_No_Data_Row_And_Page_Spacer", _ + "Test_Report_View_Only_Shows_Delivery_Paperwork_Link_When_Kit_Is_Done", _ + "Test_KitController_Declares_PrintDeliveryPaperwork_Action" _ ) End Function @@ -139,6 +145,22 @@ Class PurpleEnvelopeReport_Tests DAL__Singleton.Initialize "Provider=Microsoft.Jet.OLEDB.4.0;Jet OLEDB:Engine Type=5;Data Source=" & dbPath & ";" End Sub + Private Sub EnsureDeliveryLabelPageTable() + On Error Resume Next + DAL.Execute "CREATE TABLE [DeliveryLabelPage] (" & _ + "[ID] COUNTER CONSTRAINT [PrimaryKey] PRIMARY KEY, " & _ + "[KitID] LONG NOT NULL, " & _ + "[PageNum] INTEGER NOT NULL, " & _ + "[Label1] MEMO, " & _ + "[Label2] MEMO, " & _ + "[Label3] MEMO, " & _ + "[Label4] MEMO, " & _ + "[Label5] MEMO, " & _ + "[Label6] MEMO);", empty + Err.Clear + On Error GoTo 0 + End Sub + Private Sub ResetDisposableDatabase() dim tempPath : tempPath = m_tempDbPath Set DAL__Singleton = Nothing @@ -204,6 +226,18 @@ Class PurpleEnvelopeReport_Tests set QueryPrecinctColorMap = map End Function + Private Function BuildDeliveryLabelPage(ByVal pageNum, ByVal label1, ByVal label2, ByVal label3, ByVal label4, ByVal label5, ByVal label6) + dim page : set page = new DeliveryLabelPage_Class + page.PageNum = pageNum + page.Label1 = label1 + page.Label2 = label2 + page.Label3 = label3 + page.Label4 = label4 + page.Label5 = label5 + page.Label6 = label6 + set BuildDeliveryLabelPage = page + End Function + Public Sub Test_FormatElectionLabel_Returns_Mon_YYYY_For_Valid_Date(T) T.AssertEqual "May-2026", PurpleEnvelopeReportHelper().FormatElectionLabel("5/26/2026"), "Expected valid dates to render as Mon-YYYY." End Sub @@ -358,6 +392,75 @@ Class PurpleEnvelopeReport_Tests Set model = Nothing End Sub + Public Sub Test_SaveDeliveryLabelPages_Persists_Seeded_Page_Data(T) + Randomize + dim jCode : jCode = NextJurisdictionCode() + Call SeedJurisdiction(jCode, "City of Lansing") + dim kitId : kitId = SeedPurpleEnvelopeKit(NextSeedKey("JOB"), jCode, "Done") + + dim pages : set pages = new LinkedList_Class + pages.Push BuildDeliveryLabelPage(1, "Jurisdiction A" & vbCrLf & "Box 1 of 2", "Jurisdiction A" & vbCrLf & "Box 2 of 2", "", "", "", "") + + KitRepository.SaveDeliveryLabelPages kitId, pages + + T.AssertEqual 1, QueryScalar("SELECT COUNT(*) FROM [DeliveryLabelPage] WHERE [KitID] = ?", kitId), "Expected one staged page row for the seeded kit." + + dim rs : set rs = DAL.Query("SELECT [PageNum], [Label1], [Label2], [Label3], [Label4], [Label5], [Label6] FROM [DeliveryLabelPage] WHERE [KitID] = ? ORDER BY [PageNum]", kitId) + T.Assert Not rs.EOF, "Expected the seeded staged-page row to be present." + T.AssertEqual 1, rs("PageNum"), "Expected staged page numbers to persist." + T.AssertEqual "Jurisdiction A" & vbCrLf & "Box 1 of 2", rs("Label1"), "Expected Label1 to persist exactly." + T.AssertEqual "Jurisdiction A" & vbCrLf & "Box 2 of 2", rs("Label2"), "Expected Label2 to persist exactly." + T.AssertEqual "", rs("Label3"), "Expected unused label slots to persist as empty strings." + + Destroy rs + Set pages = Nothing + End Sub + + Public Sub Test_SaveDeliveryLabelPages_Replaces_Existing_Pages_For_The_Kit(T) + Randomize + dim jCode : jCode = NextJurisdictionCode() + Call SeedJurisdiction(jCode, "City of Lansing") + dim kitId : kitId = SeedPurpleEnvelopeKit(NextSeedKey("JOB"), jCode, "Done") + + dim firstRunPages : set firstRunPages = new LinkedList_Class + firstRunPages.Push BuildDeliveryLabelPage(1, "Run1-Page1-Label1", "", "", "", "", "") + firstRunPages.Push BuildDeliveryLabelPage(2, "Run1-Page2-Label1", "", "", "", "", "") + KitRepository.SaveDeliveryLabelPages kitId, firstRunPages + + dim secondRunPages : set secondRunPages = new LinkedList_Class + secondRunPages.Push BuildDeliveryLabelPage(1, "Run2-OnlyPage-Label1", "Run2-OnlyPage-Label2", "", "", "", "") + KitRepository.SaveDeliveryLabelPages kitId, secondRunPages + + T.AssertEqual 1, QueryScalar("SELECT COUNT(*) FROM [DeliveryLabelPage] WHERE [KitID] = ?", kitId), "Expected the second save to replace all prior staged rows for the same kit." + + dim rs : set rs = DAL.Query("SELECT [PageNum], [Label1], [Label2] FROM [DeliveryLabelPage] WHERE [KitID] = ? ORDER BY [PageNum]", kitId) + T.Assert Not rs.EOF, "Expected a replacement staged row after the second save." + T.AssertEqual 1, rs("PageNum"), "Expected replacement rows to keep their explicit page numbers." + T.AssertEqual "Run2-OnlyPage-Label1", rs("Label1"), "Expected replacement data to overwrite prior Label1 values." + T.AssertEqual "Run2-OnlyPage-Label2", rs("Label2"), "Expected replacement data to overwrite prior Label2 values." + + Destroy rs + Set firstRunPages = Nothing + Set secondRunPages = Nothing + End Sub + + Public Sub Test_DeleteDeliveryLabelPages_Removes_All_Staged_Pages_For_The_Kit(T) + Randomize + dim jCode : jCode = NextJurisdictionCode() + Call SeedJurisdiction(jCode, "City of Lansing") + dim kitId : kitId = SeedPurpleEnvelopeKit(NextSeedKey("JOB"), jCode, "Done") + + dim pages : set pages = new LinkedList_Class + pages.Push BuildDeliveryLabelPage(1, "Delete-Label1", "", "", "", "", "") + pages.Push BuildDeliveryLabelPage(2, "Delete-Label2", "", "", "", "", "") + KitRepository.SaveDeliveryLabelPages kitId, pages + + KitRepository.DeleteDeliveryLabelPages kitId + T.AssertEqual 0, QueryScalar("SELECT COUNT(*) FROM [DeliveryLabelPage] WHERE [KitID] = ?", kitId), "Expected DeleteDeliveryLabelPages to remove all staged rows for the kit." + + Set pages = Nothing + End Sub + Public Sub Test_KitController_Post_Actions_Still_Delegate_To_Color_Update_Repositories(T) dim controllerSource : controllerSource = ReadAllText("../App/Controllers/Kit/KitController.asp") @@ -382,5 +485,17 @@ Class PurpleEnvelopeReport_Tests T.Assert InStr(viewSource, "class=""print-page-spacer""") > 0, "Expected the repeated-page spacer row to remain in the report header." T.Assert InStr(viewSource, "No precinct ballot data found for this kit.") > 0, "Expected the empty-state report message to remain unchanged." End Sub + + Public Sub Test_Report_View_Only_Shows_Delivery_Paperwork_Link_When_Kit_Is_Done(T) + dim viewSource : viewSource = ReadAllText("../App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp") + + T.Assert InStr(viewSource, "Model.Kit.Status = ""Done""") > 0, "Expected the delivery paperwork button to remain gated by Done status." + T.Assert InStr(viewSource, "PrintDeliveryPaperwork") > 0, "Expected the delivery paperwork link target to remain in the view." + End Sub + + Public Sub Test_KitController_Declares_PrintDeliveryPaperwork_Action(T) + dim controllerSource : controllerSource = ReadAllText("../App/Controllers/Kit/KitController.asp") + T.Assert InStr(controllerSource, "Public Sub PrintDeliveryPaperwork") > 0, "Expected KitController to expose the PrintDeliveryPaperwork action." + End Sub End Class %> diff --git a/_bmad-output/implementation-artifacts/tech-spec-print-delivery-paperwork-purple-envelope.md b/_bmad-output/implementation-artifacts/tech-spec-print-delivery-paperwork-purple-envelope.md new file mode 100644 index 0000000..a628991 --- /dev/null +++ b/_bmad-output/implementation-artifacts/tech-spec-print-delivery-paperwork-purple-envelope.md @@ -0,0 +1,401 @@ +--- +title: 'Print Delivery Paperwork for Purple Envelope Jobs' +slug: 'print-delivery-paperwork-purple-envelope' +created: '2026-04-01' +status: 'Implementation Complete' +stepsCompleted: [1, 2, 3, 4] +tech_stack: ['Classic ASP', 'VBScript', 'Access MDB (Jet/ACE)', 'ReportMan ActiveX', 'ADO', 'FSO', 'ASPUnit'] +files_to_modify: + - 'Data/Migrations/Migration_21_Create_DeliveryLabelPage_Table.asp' + - 'App/ViewModels/KitViewModels.asp' + - 'App/DomainModels/KitRepository.asp' + - 'App/Controllers/Kit/KitController.asp' + - 'App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp' + - 'Tests/TestCase_PurpleEnvelopeReport.asp' + - 'Data/Delivery_Labels.rep' + - 'Data/Delivery_PackingSlip.rep' +code_patterns: ['Controller GET action (parameterless Sub, reads QueryString)', 'Fresh ReportManX object per report export', 'Repository with DAL.BeginTransaction + DAL.Execute array params', 'Classic ASP migration class under Data/Migrations', 'HTML.LinkToExt for GET links'] +test_patterns: ['ASPUnit DB-backed tests using disposable MDB copies', 'Manual IIS verification required for COM/ReportMan/FSO/network share', 'Boundary testing at 1100-label increments'] +--- + +# Tech-Spec: Print Delivery Paperwork for Purple Envelope Jobs + +**Created:** 2026-04-01 + +## Overview + +### Problem Statement + +After inkjet export completes and the ImportService sets a Purple Envelope kit to `Status = "Done"`, staff still need delivery paperwork to ship the job: shipping labels showing box counts and a packing slip for signed receipt. The Purple Envelope switchboard currently has no way to generate those documents. + +### Solution + +Add a `Print Delivery Paperwork` action to the Purple Envelope workflow. When a kit is in `Done` status, the edit screen will show a button that calls a new `KitController.PrintDeliveryPaperwork` GET action. That action will: + +1. Reload the kit through `SwitchBoardPurpleEnvelopeEditFindById`. +2. Reject direct access unless the kit status is exactly `Done`. +3. Compute total shipping boxes with ceiling division by 1100 labels per box. +4. Build staged label-page rows in reverse box order, six labels per page. +5. Persist those rows into a new `DeliveryLabelPage` staging table. +6. Generate a labels PDF from `Data\Delivery_Labels.rep`. +7. Generate a packing slip PDF from `Data\Delivery_PackingSlip.rep`. +8. Save both PDFs into the existing export folder for the jurisdiction. +9. Remove staged rows after both files are successfully generated and moved. +10. Redirect back to `SwitchBoardPurpleEnvelopeEdit` with flash messaging. + +### Scope + +**In Scope:** +- New Access migration for `DeliveryLabelPage` +- New `DeliveryLabelPage_Class` view model +- New `KitRepository.SaveDeliveryLabelPages` and `KitRepository.DeleteDeliveryLabelPages` +- New `KitController.PrintDeliveryPaperwork` GET action +- New `Print Delivery Paperwork` button on `SwitchBoardPurpleEnvelopeEdit.asp` +- Reverse-order page building for any number of boxes, six labels per page +- Two new ReportMan templates and their required parameters/query contracts +- Overwrite-safe PDF output to the existing export folder +- ASPUnit coverage for staging-table persistence and source-level UI/controller guardrails + +**Out of Scope:** +- Changing how kit status becomes `Done` +- Emailing, printing, or otherwise distributing the PDFs after generation +- Generalizing this into a reusable report-generation framework +- Changing existing label export behavior outside this Purple Envelope paperwork flow + +## Context for Development + +### Codebase Patterns + +- `ImportService/TrackingDataImport.vbs` sets kit status to `Done` after inkjet export. The web app should treat that as the gate for paperwork generation, not redefine the lifecycle. +- `KitController.SwitchBoardPurpleEnvelopeEdit` already loads the exact header data this feature needs, including `JCode`, `Jurisdiction`, `JobNumber`, `Status`, and `LabelCount`. +- `App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp` already conditionally shows UI based on `Model.Kit.Status`; the new button should follow that same pattern. +- `ExportTrackingLabels` in `KitController.asp` is the nearest existing ReportMan export example, including connection-string setup, overwrite behavior, and `FSO.MoveFile`. +- `MVC/activeXdepedancies.asp` exposes a shared `ReportManager`, but `HomeController.asp` shows that creating a fresh `Server.CreateObject("ReportMan.ReportManX")` instance is acceptable when isolated state is preferable. +- `MVC/lib.Data.asp` confirms `DAL.BeginTransaction`, `DAL.CommitTransaction`, and `DAL.RollbackTransaction` are available. +- Existing purple-envelope tests in `Tests/TestCase_PurpleEnvelopeReport.asp` already use disposable MDB copies and are the right place to add repository-level coverage for this feature. + +### Files to Reference + +| File | Purpose | +| ---- | ------- | +| `App/Controllers/Kit/KitController.asp` | Add `PrintDeliveryPaperwork`; reference `ExportTrackingLabels` for ReportMan + FSO export flow | +| `App/DomainModels/KitRepository.asp` | `SwitchBoardPurpleEnvelopeEditFindById` already exposes `LabelCount`, `JCode`, and `Jurisdiction` | +| `App/ViewModels/KitViewModels.asp` | Add `DeliveryLabelPage_Class` | +| `App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp` | Add the `Done`-gated button | +| `Data/Migrations/Migration_20_Add_ColorId_To_InkjetRecords.asp` | Migration naming/style reference; next migration is `Migration_21_...` | +| `MVC/lib.Data.asp` | Confirms transaction and parameter-array behavior | +| `MVC/activeXdepedancies.asp` | Confirms ReportManX is already installed/used in the app | +| `Tests/TestCase_PurpleEnvelopeReport.asp` | Existing purple-envelope test fixture patterns to extend | + +### Technical Decisions + +1. **Use the existing Purple Envelope edit query.** + `SwitchBoardPurpleEnvelopeEditFindById` already returns the required header fields and the inkjet-record-derived `LabelCount`. No new count query is needed. + +2. **Add a dedicated staging table.** + ReportMan should read normal rows from Access rather than trying to derive reverse-ordered page slots inside the `.rep` file. + + ```sql + CREATE TABLE [DeliveryLabelPage] ( + [ID] COUNTER CONSTRAINT [PrimaryKey] PRIMARY KEY, + [KitID] LONG NOT NULL, + [PageNum] INTEGER NOT NULL, + [Label1] TEXT(500), + [Label2] TEXT(500), + [Label3] TEXT(500), + [Label4] TEXT(500), + [Label5] TEXT(500), + [Label6] TEXT(500) + ); + ``` + +3. **Repository owns staging-table lifecycle.** + `KitRepository.SaveDeliveryLabelPages` will delete existing rows for the kit and insert the new set inside one transaction. `KitRepository.DeleteDeliveryLabelPages` will remove rows after both PDFs succeed. The controller should not issue raw `DAL.Execute` cleanup for this feature. + +4. **Page-building happens in VBScript, in reverse order.** + For `totalBoxes`, start at `boxNum = totalBoxes` and fill `Label1` through `Label6` before creating the next page row. That yields page 1 containing the highest box numbers first. + +5. **Each PDF generation uses a fresh ReportManX instance.** + Use `Server.CreateObject("ReportMan.ReportManX")` separately for labels and packing slip. This avoids any parameter/template state bleed between the two exports. + +6. **Direct URL access is guarded server-side.** + The button is hidden unless status is `Done`, but the controller must also reject non-`Done` requests with a warning flash and redirect. UI-only gating is not sufficient. + +7. **Staging cleanup happens only after both PDFs are safely moved.** + If generation fails after staging rows are written, leave them in place. A subsequent run will replace them because `SaveDeliveryLabelPages` always deletes and re-inserts by `KitID`. + +8. **Match existing export-folder naming.** + Use the same `ExportDirectory & JCode & "-" & Jurisdiction & "\"` pattern already used by `ExportTrackingLabels`. Do not introduce new filename sanitization behavior in this feature. + +9. **Label text format is fixed.** + Each populated label slot is multiline text: + + ``` + {Jurisdiction} + Box {X} of {TotalBoxes} + Total Printed {InkjetCount} + Job# {JobNumber} + ``` + + Empty slots on the last page are `""`. + +10. **Output filenames are deterministic.** + - Labels PDF: `{JCode}-{Jurisdiction}_delivery_labels.pdf` + - Packing slip PDF: `{JCode}-{Jurisdiction}_delivery_packing_slip.pdf` + +## Implementation Plan + +### Tasks + +- [x] **Task 1: Add the `DeliveryLabelPage` migration** + +File: `Data/Migrations/Migration_21_Create_DeliveryLabelPage_Table.asp` + +```vbscript +<% +Class Migration_21_Create_DeliveryLabelPage_Table + Public Migration + + Public Sub Up + Migration.Do "CREATE TABLE [DeliveryLabelPage] (" & _ + "[ID] COUNTER CONSTRAINT [PrimaryKey] PRIMARY KEY, " & _ + "[KitID] LONG NOT NULL, " & _ + "[PageNum] INTEGER NOT NULL, " & _ + "[Label1] TEXT(500), " & _ + "[Label2] TEXT(500), " & _ + "[Label3] TEXT(500), " & _ + "[Label4] TEXT(500), " & _ + "[Label5] TEXT(500), " & _ + "[Label6] TEXT(500));" + End Sub + + Public Sub Down + Migration.Do "DROP TABLE [DeliveryLabelPage];" + End Sub +End Class + +Migrations.Add "Migration_21_Create_DeliveryLabelPage_Table" +%> +``` + +- [x] **Task 2: Add the staging-row view model and repository methods** + +Files: +- `App/ViewModels/KitViewModels.asp` +- `App/DomainModels/KitRepository.asp` + +Add: + +```vbscript +Class DeliveryLabelPage_Class + Public PageNum + Public Label1 + Public Label2 + Public Label3 + Public Label4 + Public Label5 + Public Label6 +End Class +``` + +Add repository methods near the end of `KitRepository_Class`: + +```vbscript +Public Sub SaveDeliveryLabelPages(ByVal kitId, ByVal pages) + dim sql, pageIt, page + sql = "INSERT INTO [DeliveryLabelPage] ([KitID],[PageNum],[Label1],[Label2],[Label3],[Label4],[Label5],[Label6]) VALUES (?,?,?,?,?,?,?,?)" + + DAL.BeginTransaction + On Error Resume Next + + DAL.Execute "DELETE FROM [DeliveryLabelPage] WHERE [KitID] = ?", CLng(kitId) + + set pageIt = pages.Iterator + Do While Err.Number = 0 And pageIt.HasNext + set page = pageIt.GetNext() + DAL.Execute sql, Array(CLng(kitId), page.PageNum, page.Label1, page.Label2, page.Label3, page.Label4, page.Label5, page.Label6) + Loop + + If Err.Number <> 0 Then + dim errNumber : errNumber = Err.Number + dim errDescription : errDescription = Err.Description + DAL.RollbackTransaction + On Error GoTo 0 + Err.Raise errNumber, "KitRepository_Class.SaveDeliveryLabelPages", errDescription + End If + + On Error GoTo 0 + DAL.CommitTransaction +End Sub + +Public Sub DeleteDeliveryLabelPages(ByVal kitId) + DAL.Execute "DELETE FROM [DeliveryLabelPage] WHERE [KitID] = ?", CLng(kitId) +End Sub +``` + +- [x] **Task 3: Add `PrintDeliveryPaperwork` to `KitController`** + +File: `App/Controllers/Kit/KitController.asp` + +Add a new parameterless GET action after `SwitchBoardPurpleEnvelopeEdit` and before `Index`. + +Implementation requirements: +- Read `Id` from `Request.QueryString("Id")` +- Load the kit through `KitRepository.SwitchBoardPurpleEnvelopeEditFindById` +- If status is not `Done`, set `Flash.Warning` and redirect back to `SwitchBoardPurpleEnvelopeEdit` +- If `LabelCount <= 0`, set `Flash.Warning` and redirect +- Verify both `.rep` files exist before writing staging rows +- Create the export folder if missing +- Build a `LinkedList_Class` of `DeliveryLabelPage_Class` rows in reverse order +- Persist rows through `KitRepository.SaveDeliveryLabelPages` +- Generate labels PDF with a fresh local `ReportMan.ReportManX` object using `PBKITID` +- Generate packing slip PDF with a second fresh local `ReportMan.ReportManX` object using parameters: + - `PJURISDICTION` + - `PJOB_NUMBER` + - `PTOTAL_PRINTED` + - `PTOTAL_BOXES` +- Delete any existing final PDFs before moving the temp outputs into place +- Call `KitRepository.DeleteDeliveryLabelPages` only after both PDFs succeed +- Set `Flash.Success` and redirect back to `SwitchBoardPurpleEnvelopeEdit` + +Concrete page-build algorithm: + +```vbscript +If inkjetCount > 0 Then + totalBoxes = Int((inkjetCount + 1099) / 1100) +Else + totalBoxes = 0 +End If + +set pages = new LinkedList_Class +boxNum = totalBoxes +pageNum = 1 + +Do While boxNum >= 1 + set page = new DeliveryLabelPage_Class + page.PageNum = pageNum + + For slot = 1 To 6 + labelText = "" + If boxNum >= 1 Then + labelText = jurisdiction & vbCrLf & _ + "Box " & boxNum & " of " & totalBoxes & vbCrLf & _ + "Total Printed " & inkjetCount & vbCrLf & _ + "Job# " & jobNumber + boxNum = boxNum - 1 + End If + + Select Case slot + Case 1 : page.Label1 = labelText + Case 2 : page.Label2 = labelText + Case 3 : page.Label3 = labelText + Case 4 : page.Label4 = labelText + Case 5 : page.Label5 = labelText + Case 6 : page.Label6 = labelText + End Select + Next + + pages.Push page + pageNum = pageNum + 1 +Loop +``` + +Connection-string and export behavior should mirror `ExportTrackingLabels`, but use local report objects: + +```vbscript +dim labelsReport : set labelsReport = Server.CreateObject("ReportMan.ReportManX") +dim slipReport : set slipReport = Server.CreateObject("ReportMan.ReportManX") +``` + +- [x] **Task 4: Add the `Done`-gated button to the Purple Envelope edit view** + +File: `App/Views/Kit/SwitchBoardPurpleEnvelopeEdit.asp` + +Insert this above the existing `Ready To Assign STIDS` block so it is visible when the job is already complete: + +```asp +<% If Model.Kit.Status = "Done" Then %> +

+ <%= HTML.LinkToExt(" Print Delivery Paperwork", "Kit", "PrintDeliveryPaperwork", Array("Id", Model.Kit.ID), Array("class", "btn btn-primary")) %> +

+<% End If %> +``` + +- [x] **Task 5: Extend ASPUnit coverage** + +File: `Tests/TestCase_PurpleEnvelopeReport.asp` + +Add: +- A DB-backed test that seeds a Purple Envelope kit, calls `SaveDeliveryLabelPages`, and verifies inserted row count and label values +- A DB-backed test that calls `SaveDeliveryLabelPages` twice for the same kit and proves the second run fully replaces the first +- A DB-backed test for `DeleteDeliveryLabelPages` +- A source-level guard test asserting the view contains the `Model.Kit.Status = "Done"` condition and `PrintDeliveryPaperwork` link +- A source-level guard test asserting `KitController.asp` defines `Public Sub PrintDeliveryPaperwork` + +- [x] **Task 6: Create the two ReportMan templates** + +Files: +- `Data/Delivery_Labels.rep` +- `Data/Delivery_PackingSlip.rep` + +`Delivery_Labels.rep` +- Connection: `TRACKINGKITLABELS` +- Parameter: `PBKITID` +- Query: + `SELECT Label1, Label2, Label3, Label4, Label5, Label6 FROM DeliveryLabelPage WHERE KitID = [PBKITID] ORDER BY PageNum` +- Layout: one detail row per page, with six multiline label areas arranged 2 columns by 3 rows + +`Delivery_PackingSlip.rep` +- Parameters only: + - `PJURISDICTION` + - `PJOB_NUMBER` + - `PTOTAL_PRINTED` + - `PTOTAL_BOXES` +- Layout: single-page packing slip with jurisdiction, job number, totals, signature line, and date line + +### Acceptance Criteria + +- [ ] **AC1 - Button visibility:** Given a Purple Envelope kit with `Status = "Done"`, when `SwitchBoardPurpleEnvelopeEdit` renders, then the `Print Delivery Paperwork` button is visible. +- [ ] **AC2 - Button hidden when not done:** Given a Purple Envelope kit whose status is anything other than `Done`, when the page renders, then the button is not visible. +- [ ] **AC3 - Direct route guard:** Given a user browses directly to `PrintDeliveryPaperwork` for a kit whose status is not `Done`, when the action runs, then it redirects back with a warning flash and produces no PDFs. +- [ ] **AC4 - Exact 1100 boundary:** Given a kit with exactly 1100 inkjet records, when paperwork is generated, then `totalBoxes = 1` and the staged row has `Label1 = "Box 1 of 1"` with `Label2` through `Label6` blank. +- [ ] **AC5 - One over boundary:** Given a kit with 1101 inkjet records, when paperwork is generated, then `totalBoxes = 2` and the staged row contains `Label1 = "Box 2 of 2"` and `Label2 = "Box 1 of 2"`. +- [ ] **AC6 - Multi-page reverse ordering:** Given a kit with 7701 inkjet records, when paperwork is generated, then two staged rows are written and page 1 contains boxes 7 through 2 while page 2 contains box 1 only. +- [ ] **AC7 - Label text contract:** Given any populated label slot, when its text is inspected, then it includes jurisdiction, `Box X of Y`, `Total Printed N`, and `Job# N` on separate lines. +- [ ] **AC8 - Labels PDF output:** Given a kit with `JCode = "01234"` and `Jurisdiction = "Anytown"`, when paperwork generation succeeds, then `01234-Anytown_delivery_labels.pdf` exists in `ExportDirectory\01234-Anytown\`. +- [ ] **AC9 - Packing slip PDF output:** Given the same kit, when paperwork generation succeeds, then `01234-Anytown_delivery_packing_slip.pdf` exists in the same export folder. +- [ ] **AC10 - Overwrite behavior:** Given one or both delivery PDFs already exist, when paperwork is generated again, then the existing files are replaced without error. +- [ ] **AC11 - Success redirect:** Given both PDFs are generated and moved successfully, when the action completes, then the user is redirected back to `SwitchBoardPurpleEnvelopeEdit` with a success flash. +- [ ] **AC12 - Staging-row replacement and cleanup:** Given existing `DeliveryLabelPage` rows already exist for the kit, when paperwork is generated, then those rows are replaced by the new set and removed after both PDFs succeed. +- [ ] **AC13 - Missing template failure is handled:** Given either `.rep` file is missing, when the action runs, then the user receives a warning flash and the request does not crash with a 500. +- [ ] **AC14 - Packing slip parameters stay isolated:** Given paperwork generation runs end-to-end, when the packing slip PDF is inspected, then it shows the expected jurisdiction and job number rather than stale parameters from the labels export. + +## Additional Context + +### Dependencies + +- `ReportMan.ReportManX` ActiveX installed on the IIS host +- `FSO` and `DAL` globals already loaded through `App/include_all.asp` +- `ExportDirectory` and `dev` from `App/app.config.asp` +- `LinkedList_Class` from the shared MVC libraries +- `Delivery_Labels.rep` and `Delivery_PackingSlip.rep` present in `Data\` +- `DeliveryLabelPage` migration applied to the active MDB before use + +### Testing Strategy + +- Run ASPUnit through `Tests/Test_All.asp` after adding the repository and source-guard tests. +- Add DB-backed tests using the same disposable MDB pattern already present in `Tests/TestCase_PurpleEnvelopeReport.asp`. +- Perform manual IIS verification for: + - status `Done` button visibility + - direct-route guard for non-`Done` kits + - 1100 / 1101 / 6600 / 6601 label-count boundaries + - overwrite behavior when PDFs already exist + - missing-template warning behavior + - correct output in both dev and prod-style connection-string branches + - final export folder/file placement on the configured filesystem share + +### Notes + +- This spec is ready for implementation. The next BMAD step for this artifact is `Quick Dev` using `/bmad-bmm-quick-dev` in a fresh context. +- The report templates are the only manual-designer portion of the work. Everything else is ordinary Classic ASP / VBScript / Access code and test coverage. +- Keeping fresh ReportManX instances per PDF is the main design choice that removes the last meaningful ambiguity from the earlier review draft.