# Technical documentation — personal_laundry_20240415

Evidence date: 2026-10-06. Source: `git@bitbucket.org:overdrive/personal_laundry_20240415.git` at `4af604f` (`master`, 2024-03-19).

Passwords and connection-string secrets are not copied. The files that contain them are named so they can be rotated.

## Repository

| Fact | Value |
|---|---|
| Origin | `git@bitbucket.org:overdrive/personal_laundry_20240415.git` |
| Default branch | `master` (`origin/HEAD`) |
| Tip | `4af604f` — Dewald Stander — 2024-03-19 — "gitignore update" |
| Other branches | None |
| History | Five commits. Two "Initial commit" rows on 2020-10-26 (Abdurahmaan Ismail, then maanie), one product commit on 2023-09-04 (silmaanie), two gitignore commits on 2024-03-19 |
| Solution | `PersonalLaundry/PersonalLaundry.sln`, Visual Studio 2012 format |
| Older source control | The solution still has a Team Foundation Server binding: `http://tfs01:8080/tfs/defaultcollection` |

Code comments and Dropbox conflict filenames run from 2012 to 2023. The Bitbucket history is not the original development history.

A 2026-09-28 scan-host listing named `sonar-project.properties` and `sonarqube_scan.bat` at the repository root. Those files are not in `4af604f`.

## Layout

```text
personal_laundry_20240415/
  PersonalLaundry/          Visual Studio solution and desktop apps
    Common/                 EF model and shared types
    PersonalLaundry/        Floor WinForms app
    PersonalAdmin/          Office WinForms app
    PLFunctions/            Mail class project (see solution mismatch below)
    TestApp/                POS printer console
    PersonalFunctions/      Empty project
    PointOfService/         Empty project
    PrecompiledWeb/         Published PersonalWeb and PersonalWebBeta
    packages/               NuGet packages
    LiveBuilds/             Present as a directory
  PersonalWeb/              ASP.NET website source
  JSON/                     Vendored Newtonsoft.Json source and its tests
  Libraries/PosFor.Net/     Vendored Microsoft POS for .NET SDK and samples
  *.docx                    Process notes
  pastel excel import file layout.csv
  Personal CURRENT linen management system.7z
  20140424_195954.jpg
```

`AcmeTemplates/`, referenced by the solution, is not in the tree.

## Applications

| Project | Kind | Framework | Output |
|---|---|---|---|
| `PersonalLaundry` | WinForms, x86 | .NET Framework 4.5 | ClickOnce product name "Personal Laundry", version `1.5.0.*`, revision 87 |
| `PersonalAdmin` | WinForms | .NET Framework 4.5 | ClickOnce version `1.2.0.*`, revision 21 |
| `Common` | Class library | .NET Framework 4.5 | `Common.dll`. Entity Framework 6.4.4 |
| `PersonalWeb` | ASP.NET Web Site | .NET Framework 4.0 | Project in the solution, physical path `..\PersonalWeb`. Debug compile target `PrecompiledWeb\PersonalWebBeta`, virtual path `/PersonalWebBeta`, development port `51033` |
| `TestApp` | Console | .NET Framework 4.0 | Printer smoke test |
| `PLFunctions` | The solution expects `PLFunctions.sqlproj` (SQL project). The tree has `PLFunctions.csproj`, .NET Framework 3.5, containing `PLSendMail.cs` | Mismatch |
| `PersonalFunctions` | Class library | .NET Framework 4.0 | `AssemblyInfo` only |
| `PointOfService` | Class library | .NET Framework 4.0 | `AssemblyInfo` only. Not the POS SDK |

Entry points:

- Floor: `PersonalLaundry/PersonalLaundry/Program.cs` runs `ConsoleForm`.
- Office: `PersonalLaundry/PersonalAdmin/Program.cs` runs `ConsoleForm` in the same namespace `PersonalLaundry`.
- Web: `PersonalWeb/Global.asax` registers routes. `Login.aspx` is the forms login page.

## Website routes

From `PersonalWeb/Global.asax`:

| Route name | URL | Page |
|---|---|---|
| `pickups-root` | `pickups` | `pickups.aspx` |
| `pickups-new` | `pickups/new` | `pickup.aspx` |
| `pickups-update` | `pickups/update/{id}` | `pickup.aspx` |
| `pickups-view` | `pickups/view/{id}` | `pickup.aspx` |
| `baglabels-edit` | `pickups/baglabels/{id}` | `baglabels.aspx` |
| `baglabels-print` | `pickups/baglabels/print/{id}` | `baglabelsprint.aspx` |
| `invoices-root` | `invoices` | `invoices.aspx` |
| `invoice-view` | `invoices/{action}/{customerid}/{id}` | `invoice.aspx` |
| `dispatch-view` | `dispatches/{action}/{customerid}/{id}` | `dispatch.aspx` |
| `report-pickup` | `reports/pickup` | `reports/pickup.aspx` |
| `mobile-customer` | `mobile/{id}` | `mobile/customer.aspx` |
| `admin-customers` | `admin/customers` | `admin/customers.aspx` |
| `admin-customer` | `admin/customers/{customerId}` | `admin/editcustomer.aspx` |
| `admin-operators` | `admin/operators` | `admin/operators.aspx` |
| `admin-export` | `admin/export` | `admin/export.aspx` |
| `admin-settings` | `admin/settings` | `admin/settings.aspx` |
| `logout` | `logout` | `logout.aspx` |

`Handlers/BarcodeHandler.ashx` is addressed as a handler, not a page route.

`App_Code` types compiled with the site: `PastelExport`, `UpdateOperatorLogin`, `UpdateCustomer`, `MobileRequest`, `M`, `JsonResult`, `Fingerprint`.

## Data access

`Common.DataModel` is an Entity Framework 6 model (`DataModel.edmx`) against SQL Server catalog `PersonalLaundry`, schema `dbo`.

The applications do not ship SQL scripts. Behaviour that the C# only reaches through a function import lives in the database.

### Tables mapped by DataModel

`Actions`, `BagReferences`, `BillingProfiles`, `CustomerInventory`, `CustomerInventoryAudit`, `CustomerLoginLog`, `CustomerLogins`, `Customers`, `CustomerWorkloadBags`, `Dispatches`, `DispatchPackages`, `DispatchStatus`, `DispatchOperator`, `DriverActions`, `DriverLog`, `InventoryItems`, `InventoryItemTypeCategories`, `InventoryItemTypes`, `InventoryItemTypeSize`, `InvoiceBags`, `InvoiceItems`, `Invoices`, `InvoiceStatus`, `Locations`, `OperatorLogins`, `OperatorRoles`, `Packages`, `Settings`, `VAT`, `WorkloadItems`, `WorkloadItemsAudit`, `Workloads`, `WorkloadsAudit`, `WorkloadStatus`, `WorkloadTypes`, `sysdiagrams`.

### Views

`vwClosedCustomerWorkloads`, `vwCustomerBagsReadyToDispatch`, `vwCustomerBagsWaitingCounting`, `vwCustomerInventory`, `vwCustomerInventoryForWorkload`, `vwCustomerInventoryWorkload`, `vwDeliveryNoteCategoriesByBagSeal`, `vwDeliveryNoteCategoriesByBagSeal2`, `vwDispatchBagPackageContents`, `vwDispatchesOutForDelivery`, `vwDispatchesReadyToPrint`, `vwExcelDataDump`, `vwInventoryItems`, `vwInventoryItemTypes`, `vwInvoiceBagSealCategories`, `vwInvoiceCountsCondemned`, `vwInvoiceCountsRecon`, `vwInvoiceItemCounts`, `vwInvoicesReadyToPrint`, `vwInvoicesReadyToPrint2`, `vwOpenCustomerWorkloads`, `vwPackageContents`, `vwPackagesReadyForDispatch`, plus the workload status and category count views used by the floor grid.

### Function imports the applications call

`AcceptCustomerBag`, `CreateDispatch`, `CreatePackage`, `CreateWorkload`, `FinishCounting`, `GenerateInvoiceFromWorkloadCounts`, `GetCustomerInventory`, `GetCustomerInventoryByCustomerIdWorkloadId`, `GetCustomerInventoryForWorkload`, `GetCustomerLoginFromGuid`, `GetInvoiceForPrinter`, `GetWorkloadInventory`, `GetWorkloadInventoryForInvoice`, `GetWorkloadInventoryForDispatch`, `GetWorkloadInventoryForDispatches`, `GetWorkloadInventoryForDispatches2016`, `GetWorkloadInventoryForDelivery`, `LoginCustomer`, `LoginOperator`, `SaveFingerprint`, `UpdateCustomerInventory`, `UpdateCustomerInventoryByPercentages`, `UpdateCustomerLogin`, `UpdateCustomerWorkloadBag`, `UpdateInventoryItemType`, `UpdateInvoiceItemCount`, `UpdateInvoiceItemBadCount`, `UpdateOperatorLogin`, `UpdateWorkloadItem`, `UpdateDispatchDelivery` (and `UpdateDispatchDelivery1`, `UpdateDispatchDelivery2`), `UpdateDispatchDeliveryId`, `UpdateDeliveryId`, `UpdateCondemnedPrintedForDispatches`, `UpdateCondemnedPrintedForDelivery`, `GetCustomerEmail`, `GetCustomerCCEmail`, `GetCustomerInvoiceById`, `GetCustomerPricingPercentages`, `GetDeliveryNoteCategoriesByBagSeal`, `GetDispatchesReadyToPrint`, `GetDispatchesReadyToExport`, `GetDuplicatePrelists`, `CheckCustomerInventoryCost`, `CheckCustomerInventoryItemCost`, `CheckCustomerInventoryItemSizeCost`, `CreateDeliveryOperator`, `rptOutstandingOrders`, `rptOutstandingOrders_Items`, `rptBagReconDeliveries`, `rptBagReconPickups`, `PLSendMail`, `SendMail`.

`GetWorkloadInventoryForInvoice_backup_20140820` is imported and named as a backup. Diagram procedures `sp_helpdiagrams` and the related `sp_*diagram*` imports are SQL Server diagram helpers, not product features.

### Older model

`PLModel.edmx` maps an earlier shape: dispatches have `DispatchItems` rather than `DispatchPackages`, and several later tables are absent. The running programs construct `DataModel`.

## Connection strings

The uncommented `DataModel` connection in the source configs that were read points at SQL Server `sqlsrv02`, catalog `PersonalLaundry`, SQL authentication. Files:

- `PersonalLaundry/PersonalLaundry/App.Config`
- `PersonalWeb/web.config`

Commented alternatives, still in those files and in `PrecompiledWeb` configs, name `LOCALHOST`, `SQL-SERVERR2` / `SQLSERVERR2`, `PL-Server`, `SIL-SRV-S2016`, `PLDCPTSOD01\SQLEXPRESS`, and a public IP with the `sa` login. One commented catalog is `PersonalLaundryTest`.

Which of these a deployed `exe.config` actually uses can differ from source. Copies under `bin\Debug` and `bin\Release` are checked in and do not all agree with `App.Config`. **Requires Confirmation** which server is live.

The SQL password, the `sa` password, and the `PLUser` password are in those files in clear text. They are not repeated here. They should be treated as compromised and rotated. Prefer a secret store or Windows authentication for any future deployment.

## Authentication

| Surface | Mechanism |
|---|---|
| Floor app | Username dropdown plus password. `LoginOperator`. Role must have `CanLogin` |
| Office app | Same procedure. Dropdown limited to `OperatorRoleId` 3. Menu load then substitutes operator id 2 |
| Website | Forms authentication cookie name `PersonalLaundry`, `cookieless="UseCookies"`. Anonymous users are denied |
| Customer | `LoginCustomer(email, password, ip)` then cookie = GUID |
| Web operator | `LoginOperator`, cookie = username. Mobile user-agent regex sends the browser to `/Mobile/`; other operators go to `admin/export` |
| Private network | `Login.aspx` `Page_Load` can set the cookie to `test@test.com` without a password. See `prd.md` FR-005 |

Fingerprint forms (`DPFP.Verification`, `DPFP` enrollment) and `SaveFingerprint` are not on the startup path.

Operator passwords are written by `Admin/Operators.aspx.cs` as the typed string. Hashing, if any, would be inside `LoginOperator` or `UpdateOperatorLogin`.

## Email

`CountCustomersLaundry.sendEmail` uses `System.Net.Mail.SmtpClient`.

| Setting | Value in source |
|---|---|
| Host in use | `smtp.office365.com` |
| Port | 587 |
| SSL | Enabled |
| Mailbox | `housekeeping@personallaundry.co.za` |
| Password | Hardcoded in `CountCustomersLaundry.cs`. Not copied here |
| Commented hosts | `192.168.100.6`, `smtp.vanilla.co.za` |

From address is settings `Company Name` and `Company Email`. Recipient rules are in `business-rules.md`.

`PersonalLaundry/PLFunctions/PLSendMail.cs` is a separate mail helper. The floor path that was read does not call it; it uses `SmtpClient` directly. Function imports `PLSendMail` and `SendMail` exist on the EF model for the database to call, if the database is configured with those CLR or SQL mail procedures. The procedure bodies are not in git.

## Printing

Microsoft Point of Service (`Microsoft.PointOfService`). The floor console claims the printer at startup, loads `Properties.Resources.PrinterLogo` as `printerLogo.bmp`, and calls `SetBitmap`. It releases the printer on close.

`TestApp` looks up device type `PosPrinter` and service name `PosPrinter`.

The POS for .NET SDK, samples, and service-object sources are vendored under `Libraries/PosFor.Net`. They are not Personal Laundry code.

## Pastel

Export is a CSV built in memory by `PersonalWeb/App_Code/PastelExport.cs`. There is no Pastel SDK. Column rules are in `prd.md` FR-023 and `business-rules.md`.

The sample file `pastel excel import file layout.csv` is a 2014 layout example (customers `BEC001` and `BON001`, documents `IN100025` and `IN100026`). Its item codes do not match the exporter's `II000-###` codes.

## Deployment and environments

- Floor and office apps are ClickOnce projects (`.csproj` publish settings, and `PersonalAdmin/bin/Debug/app.publish` is in the tree). The publish URL that a running install would contact is **Unknown** without opening the deploy manifest's install source. The console logo calls `ApplicationDeployment` update check.
- The website is an ASP.NET site project, not an SDK-style web application. `app_offline.htm` is present at `PersonalWeb/app_offline.htm`.
- `PrecompiledWeb` is a checked-in precompile. It is not a guarantee of what is deployed.
- No Docker file, no pipeline definition, and no infrastructure-as-code. The old TFS server name is the only deployment history in the solution file.
- `Common.Settings` defaults `Web Service Url` to `http://pl-server`.

Commented and active SQL server names are the only environment list. There is no `.env`.

## Logging, trace, and errors

- Floor forms append text under `C:\LogFiles\logfile_{Form}_{hour}.txt`. The logger concatenates `InnerException` and will throw if `InnerException` is null.
- `PersonalWeb/web.config`: `compilation debug="true"`, `customErrors mode="Off"`, `trace enabled="true" localOnly="false"`. Trace is therefore available to remote clients when this config is what the site runs. **Requires Confirmation** on the deployed config.
- Many pages call `Trace.Warn` with usernames and inventory counts.

## Security notes

These are observations from the tree, not a penetration test.

1. SQL passwords, an `sa` password on a public IP, and the mailbox password are committed. Rotate them. Do not copy the values into further documents.
2. The private-network Forms bypass authenticates as `test@test.com` when `ReturnUrl` is present.
3. Trace is not limited to the local machine, and custom errors are off, in the committed `web.config`.
4. Operator passwords are handled as plain strings in the website admin page.
5. `BarcodeHandler.ashx` parses `type` with `Enum.Parse` and will throw on an unknown type. It is behind the forms-auth deny, except for the bypass in item 2.
6. Email recipients and BCC addresses are hardcoded, including a personal Overdrive address.
7. `.gitignore` does not ignore `bin`, `PrecompiledWeb`, or `*.config`. Build output and secrets are in history.

## Dependencies

| Dependency | Where |
|---|---|
| Entity Framework 6.4.4 | `PersonalLaundry/Common/packages.config`. The 2023 commit bumped this package |
| AjaxControlToolkit | `PersonalWeb/web.config` tag prefix `ajaxToolkit` |
| Microsoft.PointOfService | Floor app, `TestApp`, `Libraries/PosFor.Net` |
| DigitalPersona `DPFP` | Fingerprint forms |
| Newtonsoft.Json | Vendored source under `JSON/`. The laundry projects were not confirmed to reference this tree rather than a DLL |
| `System.Deployment` | ClickOnce update on the floor console |
| Microsoft ReportViewer WinForms | `PersonalAdmin/ReportConsoleForm.cs` |

`PersonalLaundry/nuget.config` exists. Its feed URL was not required to describe the product.

## Testing and development process

- No project test suite for Personal Laundry. `TestApp` prints to a POS device.
- `JSON/Source` contains Newtonsoft.Json's NUnit projects. Those tests belong to the vendored library.
- No CI definition in the repository.
- The useful process write-up is the two Word documents at the root, plus comments signed "maanie" (and once "silmaanie") dated from 2016 through 2023.

## Monitoring and jobs

No health endpoint, no queue, no Windows service, and no scheduled job is defined in the repository. Mail is sent synchronously on Finish Counting. Pastel export is a page click.

`DriverLog` and `DriverActions` tables exist. No driver-log screen was found in the floor stations. **Unknown** which application writes them.

## Related repository

`personal_laundry_website` (`git@bitbucket.org:overdrive/personal_laundry_website.git`) is a WordPress brochure for `www.personallaundry.co.za`. It does not share this database model. Silicon Ark already has a document set for it. Do not merge the two products in later edits without new evidence.
