AL Evaluates Both Sides
Two production errors landed in your queue this morning, and they are the same bug wearing two coats. The approval check on sales lines dies with a division-by-zero error on every comment line, and the customer check dies with The Customer does not exist whenever the customer field is still blank. Both procedures guard the dangerous operation with a condition on the left of an and — and both guards are useless.
The reason is a rule of the language. AL evaluates every operand of and, or and xor before it combines them; there is no short-circuit evaluation like && in C# or and in Python. In (Qty <> 0) and (Total / Qty > Limit) the division runs first, and the question about zero is asked afterwards. A guard that must stop the second condition from running has to be a statement of its own: an if that tests the guard and only then evaluates the second condition, or an early exit for the unsafe case.
Requirements
The starter contains a codeunit named "Sales Guards" with two public procedures and one local helper:
procedure IsOverLimit(Total: Decimal; Qty: Decimal; Limit: Decimal): Boolean
procedure HasOpenOrder(CustomerNo: Code[20]): BooleanFix both procedures so that their guards actually guard, keeping the object name and both signatures exactly as they are.
Rules:
IsOverLimitreturnstruewhenQtyis not zero and the averageTotal / Qtyis strictly greater thanLimit; an average equal toLimitis not over it.Qtyis never negative, and neither areTotalandLimit.- When
Qtyis zero,IsOverLimitreturnsfalseand must not raise an error, whateverTotalandLimitare — a line without quantity has no average to compare. HasOpenOrderreturnstruewhen the customer numberedCustomerNohas at least one open sales order: aSales Headerrecord whose"Document Type"isOrder, whose"Sell-to Customer No."isCustomerNoand whoseStatusisOpen. Quotes, invoices and other document types do not count, orders in any other status do not count, and orders of other customers do not count.- When
CustomerNois blank,HasOpenOrderreturnsfalseand must not raise an error. The starter'sOpenOrderExistshelper is written for a customer that exists: it fetches the customer card with a bareGet, which raises an error when there is no such customer — and a blank number is no customer. The helper needs no change; it must simply never run for a blank number. A non-blankCustomerNois always the number of an existing customer.
Pick your object ID in the 50100–50199 range and reference other objects by name, never by ID.
What the tests check
The grading tests call IsOverLimit with a quantity of zero and a random total above the limit and expect false with no error; when the call raises an error, the failure message shows the error text. They then call it with a random quantity and limit and a total chosen so that the average lands above the limit (expect true), below it (expect false) and exactly on it (expect false). For HasOpenOrder they pass a blank number and expect false with no error, again showing the raised error if there is one; they create customers and documents with the standard test libraries and expect true for a customer with a sales order in status Open, and false for a customer whose only order is Released, for a customer whose only document is a quote, and for a customer without documents while another customer has an open order. The tests run in a real company that holds other customers and documents, so your answer must follow the rules above rather than any assumption about the data.
Learn More
- Boolean (logical) operators —
and,or,xorandnotall work on fully evaluated Boolean operands. - AL control statements —
if-then-else, nestedifstatements and theexitstatement that leaves a procedure early. - Record.Get method — why a
Getwhose return value is ignored raises a runtime error when the record does not exist. - Record.IsEmpty method — the cheapest way to ask whether a filtered set holds any record at all.
IsOverLimit with Qty = 0 by hand. Before and can combine its two sides, both sides need a value — and the right side is Total / Qty. AL has no short-circuit evaluation: a guard written as the left operand of and (or or) stops nothing.if that tests the guard first and evaluates the second condition only inside its then branch, or an if that exits with false right away when the guard fails, so the code below it only runs for the safe case.HasOpenOrder has the same shape: settle the blank case before OpenOrderExists is called, so its Get only ever runs for a real customer number. The helper itself stays as it is.