# PRD — personal_laundry

Evidence date: 2026-10-07. Source: `git@bitbucket.org:overdrive/personal_laundry.git`, `main` at `0b3d107` (2026-10-02, "Merged in Renier_Work (pull request #8)").

There is no prior PRD in Silicon Ark and no PRD inside the source repository. Requirements below come from code, SQL scripts, and the in-repo notes that agree with that code. Where notes disagree, the conflict is stated. Provenance is marked on each item.

The onboarding scan named commit `1a568b4` (2025-08-07). That commit is an ancestor of `0b3d107`. This PRD describes `0b3d107`.

## Product purpose

Personal Laundry tracks hotel and commercial laundry from receipt to delivery.

A customer sends laundry in as a workload, also called a prelist. Operators count items, pack them into packages, seal packages into a bag, and put the bag on a delivery. The customer can be invoiced from the counts. Accounting receives a Pastel export. An external RFID system (Inteltag) can write the same counts through stored procedures.

Provenance: **Inferred** from station names, SQL procedure names, and `.cursor/PROJECT_MEMORY.md`. The memory file states the same purpose in one sentence and is treated as a project note, not a signed requirement.

## Users and roles

| Role | What they do | Evidence | Provenance |
|---|---|---|---|
| Operator | Signs into the POS by company and username, then runs counting, packing, bagging, dispatch, or holdback | `LoginForm`, `ConsoleForm` | Implemented |
| Administrator | An operator whose `OperatorRole.RoleName` is `Administrator`. Can reopen a finished count and can reverse condemned counts (`OperatorRoleId` 3) | `CountCustomersLaundry`, `CountCalculator` | Implemented. The role-name check and the role-id check are both present. Whether every Administrator row is id 3 is **Unknown / Requires Confirmation** |
| Personal Admin user | Signs into Personal Admin and maintains customers, logins, operators, prices, companies, settings, and reports | `PersonalAdmin/ConsoleForm` | Implemented. The menu then replaces `operatorLogin` with `OperatorLoginId` 2. See FR-014 |
| Customer login | Signs into the website with email and password and lands on pickups | `PersonalWeb/Login.aspx.cs` calls `LoginCustomer`, then route `pickups-root` | Implemented |
| Website operator | Same login form. If the email is not a customer login, `LoginOperator` is tried. A matching operator is sent to Pastel export, or to `/Mobile/` when the user agent looks like a phone | `Login.aspx.cs` | Implemented |
| Anonymous website visitor | Denied by `web.config` (`deny users="?"`) | `PersonalWeb/web.config` | Implemented. The forms login page remains the entry point |
| Private-network caller with `ReturnUrl` | If the client IP is loopback or an RFC1918 range, and `RequireAuth` is not `true`, the site sets a forms cookie for `test@test.com` and redirects. No password is checked | `Login.aspx.cs` `Page_Load` | Implemented. Whether this is still wanted in production is **Requires Confirmation** |
| Inteltag / RFID | External system. Not a user in this repository. Calls stored procedures such as `GetItemCostForReceiving` | `Scripts/GetItemCostForReceiving.sql`, `scope/IntelTag_MultiPrelist_What_Changed.md` | Documented in the IntelTag note. The 8 April 2025 integration document is not in the repo |
| Driver | An operator username posted to `Mobile/MobileHandler.ashx`. The handler writes a `DriverLog` with action, customer, and location | `MobileHandler.ashx` | Implemented |

Operator roles that can log into the POS are those with `CanLogin`. Personal Admin's operator menu only lists `OperatorRoleId > 1` and can move an operator onto a role that cannot log in. Provenance: **Implemented** (`OperatorsMenuPanel`, `OperatorLoginsForm`). The meaning of role id 1 is **Unknown / Requires Confirmation**.

## Main workflows

### W1 — POS sign-in

Trigger: an operator opens the POS.

Implemented behaviour (`LoginForm`):

- Companies and usernames load in separate calls. If one fails, the other can still populate, and a message shows the database error.
- The operator picks a company and a username, then a password.
- Company id is passed into every station. Company id 2 switches the console chrome to Texcare. Any other id keeps Personal Laundry chrome.

Idle rule: the console restarts the application after 10 minutes without input. A comment says this was specified by TK. Provenance: **Implemented**, and the comment is the only statement of who asked (`ConsoleForm.UpdateTimer_Tick`).

ClickOnce: clicking the console logo checks for an update.

### W2 — Count a workload

Trigger: the operator opens Counting.

Implemented behaviour (`Station_01_Counting`):

- The station lists workloads for the active customer whose `WorkloadStatusId` is 1, 2, 3, or 4.
- If exactly one workload matches, counting starts immediately.
- Finishing a count calls `FinishCounting`, which sets `InvoiceStatusId` to 2 and `WorkloadStatusId` to 4.
- The operator can be asked to print trolley notes.
- On finish, the station sends email when the count-recon view or the condemned view has rows. See FR-020.
- Opening a workload already at status 4 requires role name `Administrator`, a configured manager code, and a matching code. On close, the station calls `SyncInvoiceItemsFromWorkload` and writes `PostCountEditAudit`.

Manager code rule: digits 0–9 only, maximum 12. Stored as a SHA-256 hash in `Settings` under `PostCountEditManagerCode`. Set from Personal Admin Settings. Provenance: **Documented** in `CHANGELOG.md` and **Implemented** in `Common/Settings.cs` (named by the changelog).

Display names for workload statuses 1, 2, and 3 are **Unknown**. Status 4 is "finished counting" in comments and in `FinishCounting.sql`. Status 5 is set when a bag is sealed (`CreateDispatch`). Status 6 is assigned in `zzDispatchForm`, which the `zz` prefix marks as a legacy form. Whether status 6 is still used by the live `DispatchForm` was not confirmed in this pass.

### W3 — Pack

Trigger: the operator opens Packing (`CreatePackage`).

Implemented behaviour:

- Packages are created against invoice items on the counting invoice.
- Defective items use the same `CreatePackage` path and then set defective counts and `QualityEvents`. The holdback notes say not to invent a separate defective API. Provenance: **Documented** (`NewKnowledgeBase.md`) and consistent with holdback calling the same path.
- Open Prelist Finder searches open prelists and can open `PackageInvoice` without creating a bag (`createPackage` null). The close path must tolerate that null. Provenance: **Documented** and **Implemented** (`OpenPrelistFinder`, changelog note on `PackageInvoice`).

### W4 — Holdback allocate

Trigger: the orange Holdback button on the console. It opens On Hand, not the finder alone.

Implemented behaviour (`HoldbackService`, `HoldbackOnHandForm`, `HoldbackAllocateForm`):

- The operator builds an on-hand pool: customer, type, size, condition (Clean / Stain / Defective), and quantity.
- Duplicate lines of the same type, size, and condition are merged.
- Continue opens Allocate. Demand is open prelist lines for that customer, newest first (LIFO).
- Remaining capacity matches packing: invoice item quantity minus already packed clean, stain, and defective counts.
- Apply calls `CreatePackage` for each allocation. Defective allocations follow the pack defective path.
- Empty demand shows an empty LIFO message. Apply stays disabled when there is nothing to allocate.

Provenance: **Implemented** and **Documented** (`CHANGELOG.md`, `NewKnowledgeBase.md`). The changelog labels this Holdback Phase 1 at console version `v1.5.0.127`. The button caption in the designer is now `v1.5.0.132`.

### W5 — Bag and seal

Trigger: the operator opens Bagging (`BagForm`).

The bagging list loads invoices with `InvoiceStatusId` 3 for the selected company and active customers (`BagForm`). How an invoice moves from status 2 (set by `FinishCounting`) to status 3 is **Unknown / Requires Confirmation**. It is not in `FinishCounting.sql`.

Seal calls `CreateDispatch` with a comma-separated package id list, a bag seal, and the operator id.

Current rule (`Scripts/CreateDispatch.sql` and `Scripts/Phase2_MultiPrelistBag.sql`):

- At least one invoice must be present.
- All packages must belong to one customer.
- None of the packages may already be on a dispatch.
- A representative `Dispatches.InvoiceId` is stored as the minimum invoice id on the packages. It is not a promise that the bag holds only that prelist.
- Every workload represented by those packages is set to `WorkloadStatusId` 5.
- Dispatch status is inserted as 1 and then updated to 2.
- If the customer check fails, the procedure returns no dispatch row.

A bag may hold several prelists for that one customer. Bag contents are the `DispatchPackages` rows. Provenance: **Implemented** in SQL and **Documented** in `scope/IntelTag_MultiPrelist_What_Changed.md`.

An earlier design created a second invoice per seal (`IsDeliveryInvoice = 1`) and link tables `BagPrelistLink` and `DispatchInvoiceLink`. `Phase2_MultiPrelistBag.sql` does not create those objects. `scope/Why_The_Delivery_Invoice_Exists.md` ends by saying they must not be recreated. Provenance of the removal: **Documented**. Whether production data still contains those rows is **Unknown / Requires Confirmation**.

`Customers.DoublePrintBagLabel` causes a second bag-label print. Provenance: **Implemented** (`BagForm`, `BagInvoiceForm`).

### W6 — Dispatch and print

Trigger: the operator opens Dispatch.

Implemented behaviour (`DispatchForm` and `phase2-multi-prelist-verification.md`):

- `GetDispatchesReadyToPrint` returns one row per `DeliveryId`.
- The operator marks a dispatch out for delivery by setting `DispatchStatusId` to 3. A comment in `DispatchForm` names that status "Out for delivery".
- First print of a delivery is a tax invoice. The verification note says every counting invoice on that delivery is then `Printed = 1`.
- An immediate reprint is a copy tax invoice.
- Tax invoice lines group by category: Laundry, Linen, and Food and Bev. The same SKU merges when the unit cost matches. A prelist number is not a section header on the tax invoice.
- Bag slips still group by prelist number (`Pre List #`).
- A one-prelist slip uses the receiving `DateCreated` on the top line.
- The web invoice page groups the same three categories and, after `bdfae9e`, can show sections when a delivery covers more than one prelist (`PersonalWeb/Invoice.aspx.cs`).

The verification note's manual checks are **Documented** expected behaviour. The same note says the 2026-09-08 UAT did not open the WinForms dispatch screen. Treat the print wording as documented intent that the web invoice code supports for categories, and confirm the POS slip wording on a till.

### W7 — Customer website

Trigger: a person opens the site and signs in.

Routes (`Global.asax`):

| Route | Page | Who |
|---|---|---|
| `pickups` | List pickups | Customer login |
| `pickups/new`, `pickups/update/{id}`, `pickups/view/{id}` | Create or open a pickup | Customer login |
| `pickups/baglabels/{id}` and print | Bag labels | Customer login |
| `invoices` and `invoices/{action}/{customerid}/{id}` | Invoice list and invoice | Authenticated user |
| `dispatches/{action}/{customerid}/{id}` | Dispatch / delivery slip | Authenticated user |
| `admin/customers`, `admin/customers/{customerId}` | Customers and Pastel code | Operator |
| `admin/operators` | Operators | Operator |
| `admin/export` | Pastel export | Operator (default landing) |
| `admin/settings` | Settings | Operator |
| `mobile/{id}` | Mobile customer page | Operator on a phone-like browser |
| `reports/pickup` | Pickup report | Authenticated user |

Customer passwords are stored on `CustomerLogins` and compared by `LoginCustomer`. The column is named `Password`. Whether it is hashed is **Unknown / Requires Confirmation** from this pass; the login call passes the typed password through.

A new customer login rejects a duplicate email and a missing email (`App_Code/UpdateCustomer.cs`).

### W8 — Pastel export

Trigger: an operator opens `admin/export`, picks a date range, and exports.

Implemented behaviour (`PastelExport`, `Admin/Export.aspx.cs`):

- The export is built per delivery: `DeliveryId`, `DateDelivered`, customer, reference, and a document number.
- Customer code on the file is `Customers.PastelCode`, edited on the website as "Pastel Reference".
- Some detail rows are comma-separated. Some are tab-separated. Reference and wash-type values inside a field are joined with `"; "`.
- A second action builds an export log.

Commit `c2c2ecc` says the export was changed to semicolons. The current `PastelExport.cs` does not use a semicolon as the row delimiter. Provenance of the current separators: **Implemented**. The commit subject and the code disagree.

Dispatch barcodes include the Pastel code. Invoice barcodes do not. A comment dated 2025-03-18 records that split (`Dispatch.aspx.cs`).

### W9 — Mobile driver log

Trigger: the mobile handler receives JSON.

Required fields include an action (`a`), a username (`u`), a customer id (`c`), and a marker with address, latitude, and longitude. Unknown actions and unknown addresses are inserted. An unknown username returns "Unknown Operator". A `DriverLog` row is stored with the current time. Provenance: **Implemented** (`MobileHandler.ashx`).

Authentication of this handler beyond the username string is **Unknown / Requires Confirmation**. The handler reads the body and looks up `OperatorLogins.Username`. It does not show a forms-auth check in the handler itself. The site-wide `deny users="?"` may still apply.

## Functional requirements

### FR-001 — One counting invoice per workload

The system creates at most one invoice for a workload when `GenerateInvoiceFromWorkloadCounts` runs, and returns the invoice rows for that workload.

Evidence: `Scripts/GenerateInvoiceFromWorkloadCounts.sql`, `Scripts/Phase2_MultiPrelistBag.sql`.

Implementation: Implemented.

Confidence: High.

The procedure inserts only when no invoice exists for the workload. It does not filter on `IsDeliveryInvoice`. If leftover delivery invoices still share the workload id, this procedure can see more than one row. The cleanup script exists to remove those leftovers. Production state is **Unknown / Requires Confirmation**.

### FR-002 — Finish counting

Finishing a count sets the workload to status 4 and its invoices to status 2, and records the operator.

Evidence: `Scripts/FinishCounting.sql`.

Implementation: Implemented in the script. The script also contains a large commented email block. Live email is sent from the POS (`CountCustomersLaundry.sendEmail`), not from this procedure.

Confidence: High.

### FR-003 — Multi-prelist bag, one customer

Sealing a bag creates one dispatch when every package is for the same customer and none is already dispatched. Workloads on the bag move to status 5. `Dispatches.InvoiceId` is the minimum invoice id, not the full contents.

Evidence: `Scripts/CreateDispatch.sql`.

Implementation: Implemented in the script shipped on `main`. Deployment onto the production database is **Unknown / Requires Confirmation**.

Confidence: High for the script. Medium for production.

### FR-004 — Mixed-customer bag is rejected

If the packages belong to more than one customer, `CreateDispatch` does not insert a dispatch and returns an empty result.

Evidence: the `@CustomerIds = 1` predicate. Verification note scenario 2.

Implementation: Implemented in SQL. The verification note says the WinForms bagging form was not clicked in UAT.

Confidence: High for SQL.

### FR-005 — Holdback allocation is LIFO and uses pack capacity

Holdback plans allocations newest open prelist first, and will not allocate more than the unpacked remainder on an invoice line.

Evidence: `HoldbackService.BuildAllocationPlan`.

Implementation: Implemented.

Confidence: High.

### FR-006 — Reopen finished count

Only an Administrator can open a status-4 workload, and only after the manager code matches. Closing that session syncs missing invoice items and writes one audit row.

Evidence: `CountCustomersLaundry`, `CHANGELOG.md`, `Scripts/CreatePostCountEditAudit.sql`, `Scripts/SyncInvoiceItemsFromWorkload.sql`.

Implementation: Implemented, provided the SQL scripts have been applied. Script application is **Unknown / Requires Confirmation**.

Confidence: High for the POS code.

### FR-007 — Add items after counting is finished

`UpdateWorkloadItem` allows add or change when workload status is 1 or 4.

Evidence: `CHANGELOG.md` describing `Scripts/UpdateWorkloadItem.sql`.

Implementation: Documented. The changelog is the requirement statement; the script is in `Scripts/`.

Confidence: High.

### FR-008 — Tax invoice categories

A tax invoice groups lines into Laundry, Linen, and Food and Bev. Lines need a cost to appear in those groups. The same item type, size, category, and cost collapse into one line.

Evidence: `PersonalWeb/Invoice.aspx.cs`. Verification note check 8.

Implementation: Implemented on the website. POS slip layout is described in the verification note.

Confidence: High for the website.

### FR-009 — Bag slip keeps prelist sections

Bag slips group by prelist number. The prelist number is `Workloads.Reference` when that value is non-blank, otherwise the workload id.

Evidence: `vwBagPrelistContents` and `vwDeliveryNoteByPrelistBagSeal` in `Phase2_MultiPrelistBag.sql`. Verification note: "Bag slips still use `Pre List #`."

Implementation: Implemented in the views.

Confidence: High.

### FR-010 — Order reference is the workload reference

The Order Reference Report lists workloads with a non-blank `Workloads.Reference`, the workload created date, the client name, and the capturing operator username. It does not use per-bag unique references.

Evidence: `CHANGELOG.md`, `NewKnowledgeBase.md`, `ReportConsoleForm`.

Implementation: Implemented.

Confidence: High.

A non-empty reference search matches `Workloads.Reference` by case-insensitive substring and ignores the customer and date filters. An empty search uses customer and date range. Customer id 0 means all customers. Provenance: **Documented** and **Implemented**.

### FR-011 — Other admin reports

Personal Admin can run Outstanding Order Report and Bag Recon Report with the same customer and date filters.

Evidence: `ReportConsoleForm.loadReport`.

Implementation: Implemented. RDLC resources `rptOutstandingOrderSubReport2.rdlc` and `rptBagReconReport.rdlc`.

Confidence: High.

`Scripts/Phase2_PersonalAdminReports.sql` is the script named for keeping outstanding-item totals off delivery invoices. After delivery invoices are removed, that predicate is only needed if the column still exists. **Requires Confirmation** against the production schema.

### FR-012 — Companies

Admin can list companies and add a company. POS login selects a company. Data shown on the floor is filtered by that company id (bagging query joins `CompanyID`).

Evidence: `PersonalAdmin/ConsoleForm`, `BagForm`.

Implementation: Implemented.

Confidence: High.

### FR-013 — Customers active and inactive

Admin splits customers into active and inactive menus. Both can open the customer, inventory, and logins. A customer can be added from either menu.

Evidence: `PersonalAdmin/ConsoleForm` comment dated 2023-08-31 and the menu build.

Implementation: Implemented.

Confidence: High.

Counting and emails load the customer only when `IsActive` is true.

### FR-014 — Admin menu identity

While building the menu, Personal Admin sets `operatorLogin` to `OperatorLogins` where `OperatorLoginId` is 2, overwriting the person who signed in.

Evidence: `PersonalAdmin/ConsoleForm.PopulateMenu`.

Implementation: Implemented.

Confidence: High that the code does this. Whether operator 2 is a deliberate service account is **Requires Confirmation**. Treat it as a likely defect until a person confirms it.

### FR-015 — Price increase

Admin has a Price Increase menu item that opens `PriceIncreaseForm`.

Evidence: `ConsoleForm` comment "Added 2016/04/21".

Implementation: Implemented as a screen. The pricing formula was not fully read in this pass. **Unknown** beyond the screen existing.

Confidence: Medium.

### FR-016 — Website customer pickup

A signed-in customer login can list, create, update, and view pickups, and can print bag labels.

Evidence: `Global.asax` routes and the matching aspx pages.

Implementation: Implemented.

Confidence: High that the pages and routes exist. Field-level validation inside each page was not exhaustively listed.

### FR-017 — Pastel customer code

Each customer can store a Pastel code. The export writes that code as the customer code.

Evidence: `EditCustomer.aspx`, `PastelExport`.

Implementation: Implemented.

Confidence: High.

### FR-018 — Dispatch barcode versus invoice barcode

Dispatch barcodes include the Pastel code. Invoice barcodes do not.

Evidence: comment in `PersonalWeb/Dispatch.aspx.cs` dated 2025-03-18.

Implementation: Implemented according to that comment. The barcode handler is `Handlers/BarcodeHandler.ashx`.

Confidence: Medium. The comment is explicit; the handler body was not line-reviewed in this pass.

### FR-019 — Session idle lock on the POS

After 600000 ms without input the POS restarts, which returns the operator to login.

Evidence: `ConsoleForm.UpdateTimer_Tick`.

Implementation: Implemented.

Confidence: High.

### FR-020 — Count discrepancy and condemned emails

When a count is finished, if `vwInvoiceCountsRecons` has rows, the POS sends a count-recon email. If `vwInvoiceCountsCondemneds` has rows, it sends a condemned-items email.

Templates come from settings:

- `Email-Subject: CountRecon Issue` and `Email-Body: CountRecon Issue`
- `Email-Subject: Condemn Issue` and `Email-Body: Condemn Issue`
- From address: `Company Name` and `Company Email`

The body substitutes `[WorkOrderID]` and `[Items]`. The work order id is `Workloads.Reference` when set, otherwise the workload id.

Recipient rule, matched on `Customers.Name`:

- `S.S.W.F`, `Capital Mirage Hotel`, or `The Stack`: the customer's primary email.
- Any other active customer: a hardcoded internal address. A comment labels this branch "dev".

CC: `GetCustomerCCEmail`, split on semicolons, for the named customers and the fallback alike when the CC string is non-empty.

BCC: two hardcoded addresses on every send.

SMTP: Office 365, port 587, SSL. The mailbox and password are hardcoded in `CountCustomersLaundry.sendEmail`. They are not repeated in this PRD.

Provenance: **Implemented**. The customer-name list is a comment dated 2016-09-16, still present in the code. Whether those three names are still correct is **Requires Confirmation**.

### FR-021 — Inteltag keeps calling the same procedures

Inteltag should still call the procedures named in the 8 April 2025 integration document, with the same parameters. It must read bag contents from `DispatchPackages` rather than assuming `Dispatches.InvoiceId` is the only prelist.

Evidence: `scope/IntelTag_MultiPrelist_What_Changed.md`.

Implementation: the SQL side of that contract is in `Phase2_MultiPrelistBag.sql`. Whether Inteltag has been changed is **Unknown / Requires Confirmation**.

Confidence: High for the written contract. Low for the external system.

### FR-022 — Pseudo printer for development

When no thermal printer is attached, a POS for .NET service object named `PosPrinter` can render slips on screen. The POS printer initialisation does not need a code change to use it.

Evidence: `PersonalLaundry/PseudoPosPrinter/README.md`.

Implementation: Implemented as a separate project. It is a development tool, not a floor feature.

Confidence: High.

## Integrations

| System | Purpose | Direction | Trigger | Auth | Failure |
|---|---|---|---|---|---|
| SQL Server `PersonalLaundry` | System of record | Both | Every screen | SQL login in config files | POS login shows a message if a dropdown query fails. Counting logs to `C:\LogFiles` |
| Office 365 SMTP | Count-recon and condemned mail | Outbound | Finish count, when the views have rows | Hardcoded mailbox | Message box "Count Email Error"; log file under `C:\LogFiles` |
| Pastel | Accounting import file | Outbound file | Operator runs export | None in repo | Export page shows the result string when set |
| Inteltag | RFID count and cost | Inbound to SQL | External scan at count | **Unknown** | **Unknown** |
| Microsoft POS for .NET | Receipt, bag, and note printing | Outbound | Station print actions | Local device | Pseudo printer README describes the dev substitute |
| ClickOnce | POS and Admin updates | Inbound | Logo click, or Admin version label | Deployment manifest | Message box if the deployment is corrupt or the exe is not ClickOnce |

## Important constraints

- Do not recreate `IsDeliveryInvoice`, `BagPrelistLink`, `DispatchInvoiceLink`, or `GenerateDeliveryInvoicesForBagSeal`. Provenance: **Documented** (`Why_The_Delivery_Invoice_Exists.md` closing section, and the header of `Phase2_MultiPrelistBag.sql`).
- `Dispatches.InvoiceId` is a representative counting invoice. Reports and Inteltag must not treat it as the only prelist on the bag.
- `NewKnowledgeBase.md` still says Phase 2 is not in the build. That sentence is stale relative to `main`.
- Secrets are stored in source (SMTP password, SQL passwords in `web.config`, including commented historical strings). This PRD does not copy them. They need rotation. See technical documentation.
- `web.config` has `customErrors mode="Off"` and `trace enabled="true" localOnly="false"`.
- The solution references `AcmeTemplates\acme_v_2_1_3_html`, which is not in the checkout. Building the full solution on a clean machine will miss that website project.

## Requirements that could not be confirmed

- Production host, production database name confirmation (scripts all `USE [PersonalLaundry]`), and whether cleanup has been run there.
- Human-readable names for every workload, invoice, and dispatch status id.
- The step that sets `InvoiceStatusId` from 2 to 3 before bagging.
- Whether customer passwords and operator passwords are hashed.
- Whether the private-network auto login is intentional.
- The full Pastel column layout and which Pastel version consumes the file.
- Contents of the Inteltag integration document dated 8 April 2025.
- Whether Holdback Phase 2, as drawn in `holdback-module-pseudocode.md`, is still a desired future or has been replaced by the no-delivery-invoice bag design. The code has taken the replacement path.
