--- 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.