Sales Discount Rules
Guide to creating selling price, item discount, and sales invoice discount rules with formulas executed by ExpressionCalc.
Menu Route
Settings Business Rules
/form/salesdiscrules
When to Use This Form
This form is used to create the sales discount rule master data. Rules do not create transactions directly; rules are read by sales transactions when the selected product or customer has a related rule.
System Overview
- Users create rules in
/form/salesdiscrules. - Product category rules are attached to the Product master data. The Customer master data must also have a rule list so the item price/discount calculation is called from the transaction screen.
- Transaction category rules are attached to the Customer master data to calculate invoice discounts.
- When the transaction changes, the frontend calls
POST /salespriceformulafor item details andPOST /salesinvoiceformulafor the header discount. - The backend takes the formula from the table
salesdiscrules, puts the variables intoExpressionCalc, then the result is sent back to the transaction form.
Prerequisites Before Creating Rules
discount_management is active so the rule field appears in the Customer and Product master data.Entry Flow
- Open the menu Settings / Business Rules / Sales Discount Rules.
- Click Add for a new rule.
- Fill in the Code, Description, and Display Name.
- Select the Category: Product for item line rules, or Transaction for invoice discount rules.
- Select the Type: Sales Price, Percent Disc, or Value Disc.
- Use the available Variables field to copy valid variable names to the clipboard.
- Use the available Functions field if you need functions such as
roundorstackeddiscount. - Write the formula in the Formula editor. The formula must return one value, not several statements.
- Save the rule.
- Attach the rule to the Product or Customer according to its category, then test it in a Sales, Sales Order, POS, Order via QR Code, or Shop transaction.
Form Fields
| Field | Required | Explanation |
|---|---|---|
Code (id) |
Yes | Numeric rule code. This code is stored in the JSON field salesdiscrules on the Customer or Product. |
Description (description) |
Yes | Long rule name. Use a name that explains the rule's condition and result, for example "Wholesale discount 12 pcs". |
Display Name (displayname) |
Yes | Short name of at most 20 characters. Used for compact display. |
Category (category) |
Yes | Product for item calculations. Transaction for invoice discount calculations. |
Type (kind) |
Yes | Sales Price produces a new selling price. Percent Disc produces a percent discount. Value Disc produces an amount discount. |
Formula (formula) |
Yes | Calculation expression evaluated by ExpressionCalc. The editor uses JavaScript mode, but the backend evaluator uses the PHPExpression parser. |
Variables (variables) |
No | Variable autocomplete from vw_salesdiscrules. When selected, the variable value is copied to the clipboard. |
Functions (functions) |
No | List of formula functions. When selected, the function template is copied to the clipboard. |
Audit (usercreate, useredit, updatetimestamp) |
No | Automatic audit fields. |
Attaching Rules to Master Data
In the Product master data, the Sales Disc field takes its options from salesdiscrules with category=0. Product category rules are used by SalesPriceFormula.
On the transaction screen, the item formula is called if the selected product has a rule and the selected customer also has a sales discount rule list. The current backend implementation evaluates the rule list from the Product for item calculations.
In the Customer master data, the Sales Disc field can store sales discount rules. Transaction category rules are used by SalesInvoiceFormula for invoice discounts.
If the Customer has no rule, the system does not call the invoice discount formula and uses the manual discount input in the transaction.
How Formulas Work in the Backend
Product Category
Endpoint POST /salespriceformula receives customer, salesman, product, unit, detail, and salestotal. The backend takes rules from product.salesdiscrules. The rule result is sent as salesprice, percentdisc, or valuedisc.
| Type | Effect in the transaction | Notes |
|---|---|---|
| Sales Price | Overrides the item selling price. | A result of 0 means the price becomes 0 in the new Shop/POS. In legacy forms, an empty or 0 result does not override the price. |
| Percent Disc | Overrides the item discount percent, then the discount amount is calculated from percent x price x qty. | Use a percent figure, for example 5 for 5 percent. |
| Value Disc | Overrides the discount amount per unit, then multiplied by the transaction quantity. | Do not return the total discount of a line unless you really want it multiplied by the quantity again. |
Transaction Category
Endpoint POST /salesinvoiceformula reads rules from customer.salesdiscrules. The backend only uses Transaction category rules of type Percent Disc or Value Disc.
| Type | Effect in the transaction | Notes |
|---|---|---|
| Sales Price | Not used for the Transaction category. | Use this type only for the Product category. |
| Percent Disc | Overrides the invoice discount percent, then the discount amount is calculated from percent x salesgross. |
A result of 5 means a 5 percent invoice discount. |
| Value Disc | Overrides the invoice discount amount. | If a value disc is returned, the discount percent is recalculated from value disc / salesgross. |
Available Variables
The variable list is taken from the view vw_salesdiscrules. Variable names follow the database column names, so the examples below are the main patterns; the full list can be searched through the available Variables field on the form.
product.[kolom], for exampleproduct.productgroupcustomer.[kolom], for examplecustomer.pricelevelsalesman.[kolom], for examplesalesman.idunit.[kolom], for exampleunit.salesprice1,unit.salesdiscdetail.[kolom], for exampledetail.salesqty,detail.pricesalestotal, the current transaction subtotal
customer.[kolom], for examplecustomer.customergroupsalesman.[kolom], for examplesalesman.idsales.[kolom], for examplesales.divisionsalesgross, total before invoice discountbrandsummary["BRAND01"], total net amount for a particular brandsuppliersummary["S001"], total net amount for a particular supplierfactorysummary["F001"], total net amount for a particular factoryproductgroupsummary["PG001"], total net amount for a particular product group
detail.[kolom] for the Transaction category, but the current SalesInvoiceFormula implementation does not pass the variable detail to the evaluator. For invoice rules, use salesgross or the summary arrays.Functions and Formula Syntax
ExpressionCalc uses the parser PHPExpression. A formula is written as a single expression and can use arithmetic, comparison, and logical operators, ternaries, property access with a dot, and array access with brackets.
| Need | Syntax |
|---|---|
| Arithmetic | +, -, *, /, %, ** |
| Comparison | ==, !=, ===, !==, >, >=, <, <= |
| Logic | &&, ||, ! |
| Condition | kondisi ? nilai_jika_benar : nilai_jika_salah |
| Property | customer.customergroup, unit.salesprice1, detail.salesqty |
| Summary array | brandsummary["BRAND01"], suppliersummary["S001"] |
| String | "GROSIR" or 'GROSIR' |
round, floor, ceil, abs, is_infinite, is_nan, is_finitetrim, ltrim, rtrim, str_replace, implode, join, explode, issetcurrentday, currentmonth, currentyear, currenthour, currentminute, currentsecondstackeddiscount(30, 40, 50) calculates stacked discounts, not the sum of 30 + 40 + 50.Formula Examples
Use Category Product and Type Sales Price.
detail.salesqty >= 12 ? unit.salesdiscprice1 : unit.salesprice1
If the quantity is at least 12, the price uses salesdiscprice1; otherwise it uses selling price level 1.
Use Category Product and Type Percent Disc.
customer.customergroup == "GROSIR" ? stackeddiscount(unit.salesdisc, 5) : unit.salesdisc
If the customer belongs to the GROSIR group, the unit's default discount is combined with an extra 5 percent discount in a tiered way.
Use Category Product and Type Value Disc.
detail.salesqty >= 10 ? 2500 : 0
The result 2500 is the discount per unit. If the user buys 10 units, the line discount value becomes 25,000.
Use Category Transaction and Type Percent Disc.
salesgross >= 1000000 ? 2 : 0
If the sales gross is at least 1,000,000, the system gives a 2 percent invoice discount.
Use Category Transaction and Type Value Disc.
brandsummary["BRAND01"] >= 500000 ? 25000 : 0
If the total net amount of goods from brand BRAND01 is at least 500,000, the system gives an invoice discount of 25,000.
Use the category and type according to the promotion's needs.
currentmonth == 12 ? 3 : 0
This formula returns 3 in December. If the type is Percent Disc, it means a 3 percent discount.
Testing Checklist
Common Problems
product.salesdiscrules.brandsummary["BRAND01"]. For invoice rules, avoid detail.[kolom] because the variable detail has not been passed to the invoice evaluator.salesprice, percentdisc, or valuedisc. If several rules use the same type, the last rule's result becomes the final result.