Ms. Aydin, the dispatcher at Brandt Building Services, wants the invoice from the Order Book to appear on the company's website as well, and later in an app for the field technicians. The question for you is: Where does it say how an invoice is calculated? The answer from the inventory (lesson M1.3) was inconvenient: in the class MainForm, a window. As long as that is so, no other program can calculate the prices without dragging a window along.

This lesson moves the price rules out of the form into a library without any Windows dependency and with tests. It is the first seam of the order from lesson M1.4 (N1), and everything else in this course builds on it: The web API, the Blazor pages and the database access will call this library later. Module 2 is a prerequisite: The Order Book runs as net10.0-windows, and the characterization tests from lesson M2.1 are your safety net. For the architecture behind this, see K4, module 4.3 (Ports and Adapters) and 4.10 (Code architecture); here we deal only with the first, small cut.

Recognizing business logic in the form

“Business logic” is what a business department would formulate as a rule: At least one hour is charged, maintenance orders cost no call-out fee, an order must be done before it is invoiced. In the Order Book, rules of this kind can be recognized by four characteristics:

Characteristic Example in the Order Book Consequence
It calculates or decides with values Labor, CallOut, Net, Gross; the check of the status before printing Belongs in the library
It knows UI technology DataRow, MessageBox, ConfigurationManager Stays in the form or is passed in as a parameter
Others call it InvoicePrint and Exports call MainForm.Net(...) The form has become a library
Rule and message are in the same sentence The test for “not done yet” and the text of the message Extract the rule, leave the text to the form

You find the third characteristic with a search that only reads:

PowerShell
Select-String -Path .\Brandt.OrderBook\*.cs -Pattern 'MainForm\.(Labor|CallOut|MaterialWithMarkup|Net|Gross)\(' |
    ForEach-Object { '{0}: {1}' -f $_.Filename, $_.Line.Trim() }
Exports.cs: decimal net = MainForm.Net(order);
Exports.cs: decimal gross = MainForm.Gross(order);
InvoicePrint.cs: Line(g, normal, right, ref y, x, lineHeight, "Labor", MainForm.Labor(_order));
InvoicePrint.cs: Line(g, normal, right, ref y, x, lineHeight, "Call-out", MainForm.CallOut(_order));
InvoicePrint.cs: Line(g, normal, right, ref y, x, lineHeight, "Material", MainForm.MaterialWithMarkup(_order));
InvoicePrint.cs: Line(g, normal, right, ref y, x, lineHeight, "Net", MainForm.Net(_order));
InvoicePrint.cs: Line(g, normal, right, ref y, x, lineHeight, "Gross", MainForm.Gross(_order));

Seven callers in two files, and neither file has anything to do with the window. That is the proof: The rules have long been shared property of the program, they just live at the wrong address. And they depend on a row from the database (DataRow) and on Settings, that is, on technology that a library should not know.

You have the rules themselves in front of you from lesson M1.2:

  • Labor is the greater of hours and one hour, times the hourly rate.
  • CallOut is zero for maintenance orders, otherwise the call-out fee.
  • MaterialWithMarkup is the material times 1.15, rounded to two places.
  • Net is the sum of the three; Gross is Net times (1 plus the VAT rate), rounded to two places.