# Technical documentation — personal_laundry

Evidence date: 2026-10-07. Source: `git@bitbucket.org:overdrive/personal_laundry.git`, `main` at `0b3d107` (2026-10-02).

Secrets are described by location only. Passwords, mailbox secrets, and full connection strings are not copied here. They are committed in the source and should be rotated.

## Architecture

Three applications share one SQL Server database through the `Common` class library.

```text
PersonalLaundry.exe          PersonalAdmin.exe         PersonalWeb (IIS)
 WinForms stations            WinForms back office       Web Forms + handlers
        \                          |                          /
         \                         |                         /
          +-------- Common (EF6 DataModel) ----------------+
                               |
                     SQL Server catalog PersonalLaundry
                     procedures in Scripts/ and PLFunctions
                               |
              +----------------+------------------+
              |                |                  |
        POS for .NET      Office 365 SMTP     Inteltag (external)
        or PseudoPosPrinter                    calls procedures
```

`PersonalLaundry.sln` is a Visual Studio 16 solution. Projects that are actually in the checkout:

| Project | Kind | Target |
|---|---|---|
| `PersonalLaundry` | WinForms EXE, ClickOnce | .NET Framework 4.8 |
| `PersonalAdmin` | WinForms EXE, ClickOnce | .NET Framework 4.8 |
| `Common` | Class library, EF model | .NET Framework 4.8 |
| `PersonalWeb` | Website project, physical path `../PersonalWeb` | .NET Framework 4.8 |
| `PLFunctions` | SQL Server Data Tools (`.sqlproj`) | Tools target v3.5 in the project file |
| `PointOfService` | Class library | .NET Framework 4.0 |
| `PseudoPosPrinter` | POS for .NET service object | .NET Framework 4.8 |
| `PseudoPosPrinter.Smoke` | Console harness | .NET Framework 4.8 |
| `PosUiTests` | SDK-style EXE | `net48`, x86, FlaUI.UIA3 4.0.0 |
| `TestApp` | EXE | .NET Framework 4.8 |

`PersonalFunctions` is a .NET Framework 4.0 project on disk and is not listed in the solution.

The solution also points at website `acme_v_2_1_3_html` under `../AcmeTemplates/`. That directory is not in the repository. A clean build of the whole solution fails on that project until the folder exists or the project is removed. Provenance: **Implemented** in the `.sln`, absent on disk.

Precompiled websites are checked in under `PersonalLaundry/PrecompiledWeb/PersonalWeb` and `PersonalWebBeta`. They can lag the source in `PersonalWeb/`. Treat `PersonalWeb/` as the source.

## Repository layout

| Path | Contents |
|---|---|
| `PersonalLaundry/` | Solution, POS, Admin, Common, printer, tests, NuGet packages |
| `PersonalWeb/` | Website source |
| `Scripts/` | SQL scripts meant to be applied to `PersonalLaundry` |
| `Database/Scripts/` | Two defective-item scripts from September 2025 |
| `scope/` | Working notes for holdback, delivery invoices, and Inteltag |
| `PersonalLaundry/documents/` | Phase 2 recovery map, verification notes, holdback pseudocode |
| `Libraries/` | POS for .NET 1.14.1 MSI, Zen.Barcode, HtmlLite, MUI resources |
| `JSON/` | Vendored Newtonsoft.Json source, CHM, and a zip. The built package used by the apps is `packages/Newtonsoft.Json.13.0.3` |
| `CHANGELOG.md`, `NewKnowledgeBase.md` | Unreleased changelog and short notes. Some sentences are stale. See project plan |
| `.cursor/` | Project memory and agent plans |

Git history in this remote starts 2024-04-15 (`aa4ab42`). The POS assembly copyright is 2012. Conflicted copies dated 2012-08-23 and 2012-08-24 are still in `PersonalLaundry/PersonalLaundry/`.

73 commits on `main`. Remote branches other than `main` do not contain commits that `main` lacks, except that feature work was already merged (see project plan).

## POS application structure

Entry is `Program.cs`, then `LoginForm`, then `ConsoleForm`.

Stations:

| Folder | Screen | Opens from |
|---|---|---|
| `Station_01_Counting` | `CountingStation`, `CountCustomersLaundry`, `CountLaundryType`, `CountCustomerBags`, `CountCalculator`, `ManagerCodeDialog` | Count button |
| `Station_02_Packing` | `CreatePackage`, `PackageInvoice`, `PackageInvoiceItem`, `PackageInvoiceType`, `OpenPrelistFinder` | Pack button; finder also from holdback |
| `Station_03_Bagging` | `BagForm`, `BagInvoiceForm` | Bag button (`DispatchItemsButton` — the button name is historical) |
| `Station_04_Dispatch` | `DispatchForm`, `PrintNotesForm` | Dispatch button |
| `Station_04_Dispatch` | `zzDispatchForm`, `zzPrintNotesForm` | Not referenced by the console buttons that were read |
| `Station_05_Holdback` | `HoldbackOnHandForm`, `HoldbackAllocateForm`, `HoldbackService` | Holdback button |

`ConsoleForm` counts, on a timer:

- Pickups ready: `vwCustomerBagsWaitingCountings`
- Packages ready: workloads in status 4 or 5
- Dispatches ready: `vwPackagesReadyForDispatches`
- Bags ready: dispatches in status 2 or 3

Logging writes hourly files under `C:\LogFiles` (`logfile_ConsoleForm_{hour}.txt` and `logfile_{hour}.txt`). Failures in the logger show a message box.

## Data access

`Common` contains an Entity Framework 6 model (`DataModel`, with designer files `PLModel.Designer.cs` and `PLModel1.Designer.cs`). Personal Admin references the `Common` project. The changelog says it used to reference `Common\bin\Debug\Common.dll`, which went stale.

Stored procedures are called through the EF context (`FinishCounting`, `CreateDispatch`, `LoginCustomer`, `LoginOperator`, `GetCustomerEmail`, and others). The procedure bodies that must be installed for current behaviour are the scripts under `Scripts/`, especially `Phase2_MultiPrelistBag.sql`.

Important tables and views named by the code and scripts:

| Object | Role |
|---|---|
| `Customers`, `CustomerLogins`, `Companies` | Customers, website logins, companies |
| `OperatorLogins`, `OperatorRoles` | POS and admin users |
| `Workloads`, `WorkloadStatus`, `CustomerWorkloadBags` | Prelists and incoming bags |
| `Invoices`, `InvoiceItems`, `InvoiceStatus` | Counting invoice and lines |
| `Packages`, `DispatchPackages`, `Dispatches` | Pack and bag |
| `QualityEvents` | Defective / quality records |
| `Settings` | Company name, company email, email templates, manager-code hash |
| `Locations`, `DriverActions`, `DriverLogs` | Mobile driver log |
| `PostCountEditAudit` | Created by `CreatePostCountEditAudit.sql` |
| `vwInvoiceCountsRecons`, `vwInvoiceCountsCondemneds` | Email sources |
| `vwBagPrelistContents`, `vwDeliveryNoteByPrelistBagSeal` | Created by the Phase 2 install script |
| `vwWorkloadStatusReportWithCounts`, `vwCustomerBagsWaitingCountings`, `vwPackagesReadyForDispatches` | Console tiles |

There is no schema-only project that creates the full database from empty. `PLFunctions` is a SQL project of functions and procedures, not a full baseline. A new environment needs a database backup or an existing `PersonalLaundry` catalog. **Unknown / Requires Confirmation** which backup is the supported baseline. The recovery notes name `PersonalLaundry_Prod_20260831.bak` as a backup that was restored under a different database name for investigation. Do not treat that name as the live database.

## Current SQL contract

`Scripts/Phase2_MultiPrelistBag.sql` is the install script for multi-prelist bags without delivery invoices. It:

1. Alters `GenerateInvoiceFromWorkloadCounts` so it inserts one invoice if none exists for the workload and returns every invoice for that workload. It does not know about `IsDeliveryInvoice`.
2. Alters `CreateDispatch` as described in the business rules.
3. Alters `GetDispatchesReadyToPrint` to one row per `DeliveryId`.
4. Creates `vwBagPrelistContents` and `vwDeliveryNoteByPrelistBagSeal`.

Companion scripts:

| Script | When |
|---|---|
| `Phase2_CleanupRetainedDeliveryInvoices.sql` | Before the install script, on a database that still has the experimental delivery invoices. Embeds ids from the August 2026 backup. The recovery map says it aborts if those ids are missing, so it must not be aimed at an unrelated local database |
| `Phase2_CleanupRetainedDeliveryInvoices_Rollback.sql` | Rollback companion |
| `Phase2_MultiPrelistBag_Revert.sql` | Drops the delivery-invoice procedure, link tables, and `IsDeliveryInvoice` if they exist, and restores older procedure bodies |
| `Phase2_MultiPrelistBag_Backup.sql` | Backup companion. Not opened line by line |
| `Phase2_PersonalAdminReports.sql` | Report predicates that exclude delivery invoices. Needed only if that column still exists |

`Scripts/CreateDispatch.sql` matches the multi-prelist body in the install script (minimum invoice id, customer count must be 1).

## Website

ASP.NET Web Forms. `Global.asax` registers friendly routes listed in the PRD. `PersonalMaster.master` is the site master.

`web.config`:

- `targetFramework` 4.8.
- `customErrors mode="Off"`.
- `trace enabled="true" localOnly="false"`. Trace output is not limited to the server.
- Forms authentication, cookie name `PersonalLaundry`.
- Authorization denies anonymous users.
- Ajax Control Toolkit.
- Entity Framework 6 provider.
- Newtonsoft.Json binding redirect to 13.0.3.
- Active connection string name `DataModel`, catalog `PersonalLaundry`, SQL authentication. The active data source in the committed file is a developer SQL Express instance. Commented strings name other hosts, including a historical public address, and they contain passwords. Do not copy those strings into documentation or into a new environment. Rotate any password that has been in git.

`compilation debug="true"` is set.

Deployment artifacts at the solution root:

- `PersonalWeb.deploy.cmd` and `PersonalWeb.deploy-readme.txt` — standard Web Deploy script. Requires `PersonalWeb.zip` next to the cmd. The zip is not in git.
- `PersonalWeb.SetParameters.xml` and `PersonalWeb.SourceManifest.xml` — parameter and manifest files for that package.

IIS site name, host header, and production URL are **Unknown / Requires Confirmation**.

## Authentication details

POS and Admin use `OperatorLogins` through EF. The website uses `LoginCustomer` then `LoginOperator`.

Customer login cookie value is `CustomerLogins.GUID`. Operator cookie value is `OperatorLogins.Username`.

`Login.aspx` private-network bypass is described in BR-010. It authenticates a fixed username without a password.

`MobileHandler.ashx` looks up the operator by the username field in the JSON body. Whether IIS still demands a forms cookie first depends on the site-wide authorization. The handler itself does not check the cookie.

Password storage (plain or hashed) for operator and customer logins was not established. The manager code is the exception: SHA-256 in `Settings`.

## Printing

Production printing uses Microsoft Point of Service. The POS calls a logical device `PosPrinter` (`ConsoleForm.InitializePrinter` to `ReceiptPrinter`, per the pseudo-printer readme).

Vendored installer: `Libraries/PosForDotNet-1.14.1.msi`. The readme says to install that MSI and not to build against the incomplete `Libraries/PosFor.Net` tree. Expected install path: `C:\Program Files (x86)\Microsoft Point Of Service\`.

`PseudoPosPrinter` registers a service object with the same logical name so the POS binary stays unchanged. It renders UPOS escapes, bitmaps, and Code 39. Install and remove scripts are `install.ps1` and `remove.ps1` and need an administrator. Output assembly: `Microsoft.PointOfService.PLPseudoPosPrinter.dll`.

Zen.Barcode is vendored for barcode drawing.

## Email

Only the counting station sends mail in the code that was read. `FinishCounting.sql` contains a commented SQL-side mail attempt. The live path is `CountCustomersLaundry.sendEmail`.

- Host `smtp.office365.com`, port 587, SSL.
- Credentials are string literals in that method. Rotate the mailbox password. Do not copy it into new files.
- From address is built from settings `Company Name` and `Company Email`.
- Failures log and show "Count Email Error".

## Pastel

No Pastel SDK or HTTP API. `PersonalWeb/App_Code/PastelExport.cs` builds text rows. `Admin/Export.aspx` lets an operator download the export and an export log for a date range. Customer code is `Customers.PastelCode`.

Row separators in the current file are comma or tab depending on the row. Semicolon-space is used only to join multi-values inside reference and wash type.

## Inteltag

External. The repository's contract note is `scope/IntelTag_MultiPrelist_What_Changed.md`. It updates an integration document dated 8 April 2025 that is not stored here.

`Scripts/GetItemCostForReceiving.sql` says Inteltag calls it at count. Other procedures Inteltag must keep calling are named in that scope note: `GenerateInvoiceFromWorkloadCounts`, `FinishCounting`, `UpdateInvoiceItemCount`, `UpdateInvoiceItemBadCount`, `CreateDispatch`.

## Versions

| Place | Value |
|---|---|
| POS `AssemblyInfo` | `1.2.0.0` |
| POS ClickOnce `ApplicationVersion` | `1.5.0.*` |
| POS ClickOnce `MinimumRequiredVersion` | `1.5.0.1` |
| Hardcoded console label | `v1.5.0.132` |
| Changelog Holdback Phase 1 | `v1.5.0.127` |
| Personal Admin `ApplicationVersion` | `1.2.0.*` |
| Personal Admin assembly | `1.0.0.0` |
| Common, TestApp, PointOfService, PseudoPosPrinter assemblies | `1.0.0.0` |

The number an operator sees on the POS is the designer label, not `AssemblyVersion`, unless a different build changes that label. Admin shows the ClickOnce publish version when `ApplicationDeployment.IsNetworkDeployed` is true.

## Configuration and environments

Connection strings live in:

- `PersonalWeb/web.config` (active string plus commented history)
- `PersonalLaundry/PersonalLaundry/App.Config`
- `PersonalLaundry/PersonalAdmin/App.Config`
- `PersonalLaundry/Common/App.Config`
- `PersonalLaundry/TestApp/app.config`
- Precompiled `web.config` copies

The phase 2 verification note says a test `App.Config` pointed at `.\SQLEXPRESS` / `PersonalLaundry_TestUpdated` and was not committed.

Named databases in notes:

| Name | Role in the notes |
|---|---|
| `PersonalLaundry` | Catalog every script `USE`s. Intended application database |
| `PersonalLaundry_Phase2_20260831` | Isolated restore of `PersonalLaundry_Prod_20260831.bak`. Not live |
| `PersonalLaundry_TestUpdated` | Clone used for the 2026-09-08 SQL UAT |

Which server is production: **Unknown / Requires Confirmation**.

## Security observations

These are facts about the tree, not a pentest.

| Observation | Where | Notes |
|---|---|---|
| SMTP password in source | `CountCustomersLaundry.sendEmail` | Rotate. History may still contain it after a code change |
| SQL passwords in source | `PersonalWeb/web.config` active and commented entries. Check the App.config files before publishing them | Rotate. Includes a commented `sa` login |
| Errors and trace exposed | `web.config` `customErrors` Off, trace not local-only | Remote clients can receive trace |
| Debug compilation | `web.config` | `debug="true"` |
| Unauthenticated forms cookie on private IPs | `Login.aspx.cs` | Fixed username `test@test.com` |
| Driver handler trusts a username in JSON | `MobileHandler.ashx` | Confirm whether forms auth wraps it |
| Logs may include exception text on a shared disk path | `C:\LogFiles` | Local tills |
| `OperatorLoginId` 2 substituted in Admin | `PersonalAdmin/ConsoleForm.cs` | Audit trail may not be the human who signed in |

No rate limiting, captcha, or row-level security configuration is present. Access control is the forms cookie plus procedures and company filters in queries. That is **Inferred** from the absence of those mechanisms in the files that were read.

## Testing

| Asset | What it is |
|---|---|
| `PosUiTests` | net48 x86 console, FlaUI.UIA3. `SqlUat.cs` contains SQL helpers aimed at status 3 workloads |
| `PseudoPosPrinter/Smoke` | Open and print without POS registration |
| `scope/Holdback Module/Task_Holdback_Phase1_Manual_Test.md` | Manual checklist expecting console `v1.5.0.127` |
| `phase2-multi-prelist-verification.md` | SQL and `MultiPrelistBagService` harness results from 2026-09-08. Explicitly not a WinForms walk-through |
| `TestApp` | Present. Not identified as the UAT harness |

No CI pipeline file (no `bitbucket-pipelines.yml`, no GitHub Actions) was found in the top-level listing. Build and release appear to be Visual Studio plus ClickOnce and Web Deploy, done from a workstation. **Inferred.**

## Monitoring and jobs

No Windows service, Hangfire, or queue project is in the solution. Background behaviour inside the apps:

- POS `UpdateTimer` refreshes the console grid and enforces idle restart.
- Email is synchronous on the counting finish path.
- ClickOnce update check runs when the logo is clicked.

There is no application health endpoint. Website trace is the only request log configured in `web.config`.

## Development process visible in the repo

- Git on Bitbucket `overdrive/personal_laundry`, branch `main`, pull requests recorded in merge subjects (#1 through #8).
- Commit style is mixed: ticket-like `[Holdback]` prefixes, `fix(invoice):` on the latest feature commit, and sentence-style subjects.
- Agents are instructed, inside the repo, to read `.cursor/PROJECT_MEMORY.md`. That file's Phase 2 sentence is behind `main`.
- `CHANGELOG.md` has a single `[Unreleased]` section and no released version headings.

## Build notes

POS, Admin, Common, and the website need .NET Framework 4.8 targeting pack and Visual Studio or Build Tools. The pseudo-printer readme shows MSBuild from `C:\Windows\Microsoft.NET\Framework\v4.0.30319\MSBuild.exe` as a fallback, which can build the 4.8 project when the targeting pack is installed.

NuGet packages are vendored under `PersonalLaundry/packages/` (`Newtonsoft.Json.13.0.3`, `EntityFramework.6.4.4`). `nuget.config` is present.

Platform: POS for .NET service objects and `PosUiTests` are x86-oriented. Confirm the POS project platform before publishing. The pseudo-printer readme assumes a 32-bit POS for .NET install under Program Files (x86).

## Items requiring human confirmation

- Production SQL instance, ClickOnce publish URL, and IIS host.
- Whether `Phase2_CleanupRetainedDeliveryInvoices.sql` and `Phase2_MultiPrelistBag.sql` have been applied to production.
- Whether Inteltag has been updated to read `DispatchPackages`.
- Whether `AcmeTemplates` should be restored or removed from the solution.
- Rotation of secrets currently stored in git.
