---
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 %>
    <p>
        <%= HTML.LinkToExt("<i class='glyphicon glyphicon-print'></i> Print Delivery Paperwork", "Kit", "PrintDeliveryPaperwork", Array("Id", Model.Kit.ID), Array("class", "btn btn-primary")) %>
    </p>
<% 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.
