# Business rules — personal_laundry_20240415

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

Rules are taken from code, from the two Word process files, or from the Pastel sample CSV. Stored-procedure bodies are not in the repository. A rule that is only visible as a procedure call is marked that way.

Labels: **Documented Requirement**, **Implemented Behaviour**, **Inferred Behaviour**, **Unknown**.

## Customers

1. **Implemented Behaviour.** A customer has a name, billing profile, contact name, three email addresses, postal address, VAT number, Pastel code, a flag to double-print the bag label, and `IsActive`. Evidence: `Common/Customer.cs`, `PersonalAdmin/CustomerForm.cs`.
2. **Implemented Behaviour.** PersonalAdmin shows `IsActive` customers under "Active Customers" and the rest under "Inactive Customers". Both menus can add a customer. Evidence: `PersonalAdmin/ConsoleForm.cs`. Comment dated 2023-08-31. Commit `c98fb21`.
3. **Implemented Behaviour.** A customer login has an email address and its own `IsActive` flag, shown as a check mark. Evidence: `CustomerLoginsForm.cs`.
4. **Unknown.** Whether an inactive customer is blocked from the website. `Pickup.aspx.cs` loads the customer from the login GUID and does not check `Customers.IsActive`.
5. **Implemented Behaviour.** Each customer inventory row stores four integer prices: general wash, repeat wash, stain wash, and rewash. Evidence: `CustomerInventory.cs`.
6. **Inferred Behaviour.** Those integers are cents. Pastel export divides `Cost` by 100 before printing a money amount. Evidence: `PastelExport.cs`.
7. **Implemented Behaviour.** A billing profile has a name and `IncludeRewashInInvoice`. Evidence: `BillingProfile.cs`. Whether invoice generation reads that flag is **Unknown** (SQL).
8. **Implemented Behaviour.** The admin inventory grid displays `ProductCode` from `GetCustomerInventory`. Saving the inventory screen calls `UpdateInventoryItemType` with category, type, and size only. Evidence: `UpdateCustomerInventory.cs`, `GetCustomerInventory_Result.cs`.
9. **Unknown.** Where `ProductCode` is stored, and whether any screen writes it.

## Operators and access

10. **Implemented Behaviour.** An operator has a username, a password field, a role, and a shift. Evidence: `OperatorLogin` usage in `PastelExport.cs` and `Operators.aspx.cs`.
11. **Implemented Behaviour.** A role has `RoleName` and `CanLogin`. Evidence: `OperatorRole.cs`.
12. **Implemented Behaviour.** The floor app lists only operators with `CanLogin` and accepts the password only if `LoginOperator` returns a row. Evidence: `PersonalLaundry/LoginForm.cs`.
13. **Implemented Behaviour.** PersonalAdmin lists only operators with `CanLogin` and `OperatorRoleId == 3`. Evidence: `PersonalAdmin/LoginForm.cs`.
14. **Implemented Behaviour.** The role *names* are not compared on the current floor console. An unused 2012 conflict copy required `RoleName == "Administrator"` for some buttons. Evidence: `ConsoleForm (Brett Meyerowitz's conflicted copy 2012-08-23).cs`.
15. **Implemented Behaviour.** After PersonalAdmin login, `PopulateMenu` replaces the signed-in operator with the row `OperatorLoginId == 2`. Price increases and other saves that use `this.operatorLogin` therefore attribute the action to operator 2. Evidence: `PersonalAdmin/ConsoleForm.cs`. **Requires Confirmation.**
16. **Implemented Behaviour.** The website operator list hides `OperatorLoginId` 1. One list also hides `OperatorRoleId` 2. Creating an operator on that page sets role id 2 and an empty shift. Evidence: `Admin/Operators.aspx.cs`.
17. **Inferred Behaviour.** Operator id 1 is a system or unassigned operator. The mobile page treats bags with `OperatorLoginId == 1` as waiting and every other id as collected. Evidence: `Mobile/Customer.aspx.cs`. The name of operator 1 is **Unknown**.
18. **Implemented Behaviour.** The website password for a new operator is stored from the text box. No hash is applied in the page. How `LoginOperator` compares passwords is **Unknown**.
19. **Implemented Behaviour.** Website access requires a Forms cookie (`deny users="?"`). Evidence: `PersonalWeb/web.config`.
20. **Implemented Behaviour.** If the login page is opened with `ReturnUrl`, the client IP is loopback or an RFC1918 address, and `RequireAuth` is not `true`, the site issues a Forms cookie for `test@test.com` and redirects. No password is checked. Evidence: `Login.aspx.cs`.
21. **Implemented Behaviour.** A customer login cookie value is the customer login GUID. An operator login cookie value is the operator username. Evidence: `Login.aspx.cs`.
22. **Implemented Behaviour.** Floor operators are signed out by restarting the app after 600000 ms without input. The comment says this was specified by TK on 2018-01-09. Evidence: `PersonalLaundry/ConsoleForm.cs`. PersonalAdmin has no equivalent check in the code that was read.

## Pickups

23. **Documented Requirement.** The customer chooses a wash type, enters towelling, linen, and food-and-beverage counts, adds a reference, and receives a successful pickup. Evidence: `PL Current linen mngt system - Client Pick Up System Trail.docx`.
24. **Implemented Behaviour.** The website groups items into categories `Laundry`, `Linen`, and `Food and Bev`. Evidence: `Pickup.aspx.cs`.
25. **Implemented Behaviour.** A new pickup only offers workload types with `CanStartCycle`. Types for which this customer already has a workload in status 1 are removed. Evidence: `Pickup.aspx.cs`.
26. **Implemented Behaviour.** On update, the type list is every `CanStartCycle` type, and the type dropdown is disabled when the action is not `new`. Evidence: same file.
27. **Implemented Behaviour.** The reference is left-padded with `0` to 8 characters before it is saved. Evidence: comment 2018-01-18 and `PadLeft(8, '0')`.
28. **Implemented Behaviour.** If any workload row already has that reference, the save is refused with "Reference already exists!". The query does not ignore the workload being edited. Evidence: `Pickup.aspx.cs`, comment 2016-10-20.
29. **Implemented Behaviour.** The customer cannot save a pickup without an operator username and password that pass `LoginOperator`. That operator is stored on `Workload.OperatorAuthoriseId`. Evidence: comment 2018-01-18.
30. **Implemented Behaviour.** Total bags are stored on the workload. Evidence: comment 2018-11-12, `Workload.TotalBags`.
31. **Unknown.** The status `CreateWorkload` assigns, and the bag rows it creates. The page redirects to bag labels only when the procedure returns a workload.
32. **Implemented Behaviour.** Viewing a pickup disables the form and hides the submit button. Evidence: `LoadInventory` when the action is `view`.
33. **Inferred Behaviour.** The Word file's "unique number per order" is this reference. The floor counting narrative uses the same number (example `32201`) to open the bags. Evidence: both Word files. The floor lookup itself is inside the counting queries and was not fully traced.

## Counting, condemned, and email

34. **Documented Requirement.** Counting is split into Laundry, Linen, and F&B, then into item types such as hand towels and standard pillow cases. Finish Counting prevents further counting once the wash starts, and the system can print trolley labels. Evidence: the linen-management Word file.
35. **Implemented Behaviour.** Finish Counting does nothing if the workload has no invoice or the invoice has no items. The operator must confirm. The app then calls `FinishCounting` and, if asked, prints trolley notes. Evidence: `CountCustomersLaundry.ContinueButton_Click`.
36. **Documented Requirement**, later **Implemented Behaviour.** A count difference should email a defined recipient. The Word file says this was not in the system. The code sends the mail when `vwInvoiceCountsRecon` returns rows. Evidence: Word file; `sendEmail`.
37. **Implemented Behaviour.** To-address rule, comment 2016-09-16:
    - Customer name `S.S.W.F`, `Capital Mirage Hotel`, or `The Stack`: the customer's primary email.
    - Any other name: `tk@personalcleaners.co.za`.
    - BCC always includes `tk@personalcleaners.co.za` and `maanie@overdrive.co.za`.
    - CC is `GetCustomerCCEmail`, split on `;`, when the first value is not empty.
    **Requires Confirmation** that these three names and the BCC list are still correct.
38. **Implemented Behaviour.** Subject and body come from settings `Email-Subject: CountRecon Issue` and `Email-Body: CountRecon Issue`. The body may contain `[WorkOrderID]` and `[Items]`. The work-order id is the reference when it is non-empty, otherwise the workload id. Evidence: `sendEmail`.
39. **Documented Requirement.** A torn or damaged piece is recorded on the count, identified as condemned, and returned unwashed rather than cleaned. The Word file says this was not in the system.
40. **Implemented Behaviour.** `InvoiceItems.BadCount` is updated by `UpdateInvoiceItemBadCount`. The website invoice prints "{n} x {size} condemned" when `BadCount > 0`. A second email uses `vwInvoiceCountsCondemned` and settings `Email-Subject: Condemn Issue` and `Email-Body: Condemn Issue`, with the same recipient rule as rule 37. Evidence: `CountCalculator.cs`, `Invoice.aspx`, `sendEmail`.
41. **Unknown.** Whether a condemned piece is excluded from the wash load or from the bill. That decision would be in `FinishCounting` or `GenerateInvoiceFromWorkloadCounts`, which are not in git.
42. **Implemented Behaviour.** `CondemnedPrinted` is updated when delivery notes are printed. A comment dated 2016-11-02 says the flag is cleared for condemned items on the first invoice or delivery. Evidence: `PrintNotesForm.cs`.

## Packing, bagging, dispatch

43. **Documented Requirement.** Package size is the size agreed with the customer. Example in the Word file: duvet covers in piles of 25. Unpacked items are red, held-back items are orange, fully packed items are green.
44. **Inferred Behaviour.** Orange means items held for rewash. The Word file says so. `BillingProfiles.IncludeRewashInInvoice` and a 2019 comment about rewash null checks support that rewash is a real path. The colour values in the WinForms designer were not transcribed.
45. **Implemented Behaviour.** The floor console treats workloads in status 4 or 5 as the pack-ready count. Evidence: `ConsoleForm.cs`. The status names are **Unknown**.
46. **Implemented Behaviour.** Bagging offers invoices in status 3, capped at 100. Evidence: `BagForm.cs`.
47. **Implemented Behaviour.** A dispatch stores a bag seal, an invoice id, a status, an optional delivery id, and an optional delivered date. Evidence: `Dispatch.cs`.
48. **Implemented Behaviour.** Dispatch selection uses status 2. The console's bag-ready count uses status 2 or 3. Evidence: `DispatchForm.cs`, `ConsoleForm.cs`.
49. **Documented Requirement.** The delivery note is printed in duplicate. One copy stays with the customer. One is signed and returned to the laundry for filing. Evidence: Word file. The printer code was not checked for a hard-coded copy count.
50. **Implemented Behaviour.** `Customers.DoublePrintBagLabel` is stored. Evidence: `Customer.cs`. Where the printer reads it was not traced in this pass.
51. **Inferred Behaviour.** One delivery (`DeliveryId`) can cover more than one dispatch, and one invoice can cover more than one bag, because those are foreign keys on `Dispatches` rather than unique columns. The Word file requested both groupings. The procedure that assigns the ids was not read.
52. **Implemented Behaviour.** A comment dated 2019-02-15 says a rewash must not take the null-check path used for a normal print. Evidence: `PrintNotesForm.cs`.

## Pricing changes

53. **Implemented Behaviour.** Applying a price increase asks "Are you sure" and states that the changes cannot be undone. Cancel or No leaves the data alone. Yes calls `UpdateCustomerInventoryByPercentages` with the current `operatorLogin` id. Evidence: `PriceIncreaseForm.cs`.
54. **Unknown.** The percentage columns and the rounding rules inside that procedure.
55. **Inferred Behaviour.** Because of rule 15, the id recorded for a PersonalAdmin price increase is operator 2, not the person who typed the password, unless that person is operator 2.

## Invoicing and Pastel

56. **Implemented Behaviour.** Pastel export runs on dispatches ready to export, using `DeliveryId`, `DateDelivered`, customer, reference, and a document number. Evidence: `Admin/Export.aspx.cs`.
57. **Implemented Behaviour.** Lines with no cost are omitted. A delivery whose remaining lines all lack a cost produces no file rows. Evidence: `PastelExport` constructor.
58. **Implemented Behaviour.** Quantity exported is good count plus stained count. Evidence: comment 2017-12-29, `ItemCount + ItemCountStain`.
59. **Implemented Behaviour.** Exclusive unit price is `Cost / 100`. Inclusive price is that amount plus VAT. Evidence: `PastelExport.cs`.
60. **Implemented Behaviour.** VAT is the first row of table `VAT`, column `VAT1`. If that row is missing, the rate used is `0.14`. Evidence: comment 2018-03-26.
61. **Inferred Behaviour.** The accounting period is a March-to-February year: January and February become periods 11 and 12; March becomes period 1. The code is `month + 10` when `month < 3`, otherwise `month - 2`. Evidence: `PastelExport.cs`. **Requires Confirmation** against the Pastel company calendar.
62. **Implemented Behaviour.** Document number printed in the file is `IN` plus the delivery id padded to 6. Order number is `"{reference} - {deliveryId}"`. Customer code is `PastelCode`. Store is the literal `Web`. Item code is `II000-` plus the item type id padded to 3. Unit is `Each`. Tax type and discount type are 1. Discount is 0. Exchange rate is 1. Amounts are exclusive (`N`). Evidence: `PastelExport.cs`.
63. **Implemented Behaviour.** Sales analysis code is the delivery operator username. Ship/deliver is that operator's shift. Freight method is the workload type's `Type` string. Evidence: comments 2017-10-03, 2017-12-14, and 2018-05-16.
64. **Implemented Behaviour.** Address fields are truncated with a helper that, when the string is longer than the limit, keeps `limit - 1` characters. Limits used: name 30, address lines 30, contact 16, description 40. Evidence: `Left` in `PastelExport.cs`.
65. **Documented** sample versus **Implemented** exporter. `pastel excel import file layout.csv` uses the same Header/Detail layout and unit `Each`, with item codes such as `01-001`. The exporter writes `II000-###`. **Requires Confirmation** which coding scheme Pastel expects. An older per-customer code (`II{customer}-###`) is commented out in the exporter.
66. **Implemented Behaviour.** The export log includes the count of invoices exported and the result of `GetDuplicatePrelists` for the date range. Evidence: `createPastelExportLog`.

## Reports

67. **Implemented Behaviour.** Outstanding Order uses `rptOutstandingOrders` and `rptOutstandingOrders_Items` and the embedded report `PersonalLaundry.rptOutstandingOrderSubReport2.rdlc`. Evidence: `ReportConsoleForm.cs`.
68. **Implemented Behaviour.** Bag Recon uses `rptBagReconPickups` and `rptBagReconDeliveries` and `PersonalLaundry.rptBagReconReport.rdlc`. Evidence: same file.
69. **Unknown.** The date rules and the definition of outstanding, which sit in the procedures.

## Settings

70. **Implemented Behaviour.** These setting names are read or written by `Common.Settings`:

| Key | Used for |
|---|---|
| `Company Name` | Documents and the email From display name |
| `Company Address` | Documents |
| `Company Phone` | Documents |
| `Company Email` | Documents and the email From address |
| `Company VAT Number` | Documents. Editable on `SettingsForm` |
| `Web Service Url` | Default `http://pl-server` when the row is missing |
| `Service Url` | Stored. Callers were not traced |
| `Email-Subject: CountRecon Issue` | Variance email subject |
| `Email-Body: CountRecon Issue` | Variance email body |
| `Email-Subject: Condemn Issue` | Condemned email subject |
| `Email-Body: Condemn Issue` | Condemned email body |

71. **Implemented Behaviour.** If a company or email setting row is missing, `sendEmail` indexes the first list element and throws. The company helpers in `Common.Settings` return an empty string instead. The two behaviours differ.

## What the Word file asked for that the code does not state

72. **Documented Requirement** only. Condemned items are returned to the customer uncleaned. No C# status was found that marks a piece as "not washed".
73. **Documented Requirement** only. Delivery notes are filed as a signed copy plus a customer copy. No filing workflow is in the code.
74. **Unknown.** Package size "agreed with the customer" is not a column on `Customer` or `CustomerInventory`. It may be an operating practice, or it may be inside `CreatePackage`.

## Conflicts

- Word file: variance email and condemned counts are future work. Code: both exist, with comments from 2016. The code is the later evidence.
- PersonalAdmin login identity versus operator id 2. See rule 15.
- Pastel item codes in the 2014 sample versus `II000-###` in the exporter. See rule 65.
- Inactive customers are separated in the office app and are not filtered on the pickup page. See rule 4.
