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:
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:
Laboris the greater of hours and one hour, times the hourly rate.CallOutis zero for maintenance orders, otherwise the call-out fee.MaterialWithMarkupis the material times 1.15, rounded to two places.Netis the sum of the three;Grossis Net times (1 plus the VAT rate), rounded to two places.