Calculation Engine¶
The calculation engine is responsible for determining how much commission a transaction should generate.
It separates transaction preparation, configuration resolution, commission evaluation, and mathematical calculation so that each part of the process has a focused responsibility.
Calculation Flow¶
The overall calculation pipeline is:
ERPNext Sales Invoice
↓
Transaction Builder
↓
Plan Resolution
↓
Rule Resolution
↓
Payee Resolution
↓
Commission Evaluation
↓
Commission Calculation
↓
Commission Ledger
The calculation engine does not replace ERPNext's Sales Invoice processing. It consumes the relevant sales transaction information and produces Commission Ledger entries.
Main Modules¶
The calculation process is divided across several modules:
| Module | Responsibility |
|---|---|
transaction_builder.py |
Converts Sales Invoice data into commission calculation inputs |
resolver.py |
Resolves plans, rules, tiers, and payees |
evaluator.py |
Coordinates resolution and calculation |
engine.py |
Applies the selected commission calculation method |
calculator.py |
Contains the low-level mathematical calculation functions |
ledger.py |
Persists the calculated result as Commission Ledger entries |
This separation keeps business rules out of the low-level calculation functions.
Step 1: Build the Transaction¶
The calculation process starts with an ERPNext Sales Invoice.
The transaction builder prepares the invoice data for commission evaluation.
For Sales Invoice items, the relevant commission base is derived from the transaction amount.
Sales Team contribution is also considered at this stage.
For example:
Invoice Item Amount = ₹10,000
Sales Person A = 60%
Sales Person B = 40%
The transaction builder produces separate allocation inputs:
Sales Person A
Base Amount = ₹6,000
Sales Person B
Base Amount = ₹4,000
This allows the same invoice item to produce separate commission calculations for different Sales Team members.
Sales Team Allocation¶
ERPNext Sales Invoices can contain one or more Sales Team members.
Each member can have a contribution percentage.
The commission engine uses that contribution to determine the amount on which the member's commission is calculated.
For example:
Invoice Amount = ₹20,000
Sales Person A = 70%
Sales Person B = 30%
The allocated bases are:
A = ₹20,000 × 70% = ₹14,000
B = ₹20,000 × 30% = ₹6,000
The resulting commission records retain the transaction context needed to understand how the base amount was produced.
Step 2: Resolve the Commission Plan¶
After the transaction is prepared, the resolver determines which Commission Plan applies.
The plan must be applicable to the transaction's:
- Company
- transaction date
- active status
The validity period is evaluated using valid_from and valid_to.
When multiple applicable plans exist, the plan with the most recent valid_from is selected.
Conceptually:
Applicable Plans
│
├── Plan A — valid from Jan 1
├── Plan B — valid from Apr 1
└── Plan C — valid from Jul 1
▲
│
selected plan
For a transaction in August, Plan C would take precedence if all three plans are otherwise applicable.
This allows newer plans to supersede older plans without modifying historical commission records.
Step 3: Resolve the Commission Rule¶
Once the plan is selected, the engine evaluates the Commission Rules belonging to that plan.
A rule can specify what the commission applies to.
Supported rule conditions include:
- Item
- Item Group
- Sales Person
- Customer
- Customer Group
- Territory
The rule also contains the calculation method and its associated configuration.
For example:
Commission Plan
└── Standard Sales Commission
│
├── Rule: Premium Smartphone
│ Applies to: Item
│ Item: Smartphone
│
└── Rule: Electronics Group
Applies to: Item Group
Item Group: Electronics
Rules are evaluated according to their priority.
Lower priority numbers are evaluated first when multiple rules match.
Rule Matching¶
The resolver checks the condition represented by based_on and compares it with the transaction context.
For example, an Item rule can be conceptually evaluated as:
Rule Applies To = Item
Rule Item = Smartphone
Transaction Item = Smartphone
↓
Match
A non-matching rule does not produce a commission calculation.
The selected rule supplies the calculation configuration to the evaluator.
Step 4: Resolve the Commission Payee¶
The engine determines who should receive the commission from the transaction's Sales Team information and the configured Commission Payee.
A Commission Payee represents the recipient of commission.
The application supports:
- Employee payees
- Sales Partner payees
For an employee-based commission, the payee is linked to the relevant ERPNext Sales Person.
The payee resolution therefore connects:
Sales Invoice
↓
Sales Team
↓
Sales Person
↓
Commission Payee
The resolved payee is stored on the resulting Commission Ledger entry.
Step 5: Determine the Calculation Base¶
The calculation base is the transaction amount to which the commission method is applied.
For a simple single-person allocation:
Item Amount = ₹10,000
Sales Team Contribution = 100%
Base Amount = ₹10,000
For a partial contribution:
Item Amount = ₹10,000
Sales Team Contribution = 50%
Base Amount = ₹5,000
The base amount is recorded on the Commission Ledger so that the historical calculation can be understood later.
Calculation Methods¶
The engine supports three primary calculation methods:
- Percentage
- Fixed Amount
- Tiered
The selected Commission Rule determines which method is used.
Percentage Calculation¶
Percentage calculation applies a commission rate to the calculated base amount.
The basic formula is:
Commission = Base Amount × Rate / 100
For example:
Base Amount = ₹10,000
Rate = 5%
Commission = ₹10,000 × 5 / 100
= ₹500
The resulting commission amount is stored in the ledger.
Partial Sales Team Allocation¶
If the Sales Person receives 60% contribution:
Invoice Amount = ₹10,000
Contribution = 60%
Base Amount = ₹6,000
Rate = 5%
Commission = ₹6,000 × 5 / 100
= ₹300
The commission is therefore based on the Sales Person's allocated transaction value rather than automatically using the full invoice item amount.
Fixed Amount Calculation¶
A fixed commission does not depend on the percentage of the transaction amount.
Instead, the Commission Rule specifies a fixed commission amount.
Conceptually:
Fixed Commission = Configured Fixed Amount
For example:
Fixed Amount = ₹250
Commission = ₹250
The fixed amount is stored with the resulting ledger entry.
The base transaction information is still retained so that the commission can be traced back to the source transaction.
Tiered Calculation¶
Tiered commission applies different rates according to the transaction amount.
A Commission Rule using the Tiered calculation method contains Commission Tier child records.
A tier contains:
- From Amount
- To Amount
- Rate
- Description
A final tier can leave To Amount empty, meaning there is no upper limit.
For example:
From To Rate
₹0 ₹10,000 2%
₹10,000 ₹20,000 3%
₹20,000 blank 5%
The tier configuration determines which rate applies to the calculation.
Tier Selection¶
The engine compares the calculation amount with the configured tier ranges.
Conceptually:
Transaction Amount
│
▼
Find matching tier
│
▼
Use tier rate
│
▼
Calculate commission
The selected rate is recorded on the resulting Commission Ledger entry.
Decimal-Based Calculations¶
Commission calculations use decimal arithmetic rather than relying on binary floating-point arithmetic.
This is important for financial calculations because decimal values need predictable monetary precision.
The calculation layer therefore works with Decimal values for commission-related arithmetic.
Conceptually:
Transaction Amount
↓
Decimal
↓
Calculation
↓
Commission
This helps avoid unexpected floating-point rounding behavior.
Evaluator Responsibilities¶
The evaluator acts as the coordinator between transaction data, configuration resolution, and calculation.
Conceptually:
Evaluator
│
├── Resolve Plan
│
├── Resolve Rule
│
├── Resolve Payee
│
├── Determine Base
│
└── Calculate Commission
The evaluator should not need to implement every mathematical calculation itself.
Instead, it supplies the appropriate inputs to the calculation layer.
Engine Responsibilities¶
The engine provides the calculation layer used by the evaluator.
It selects the appropriate calculation behavior based on the Commission Rule's calculation method.
Conceptually:
Calculation Method
│
├── Percentage → percentage calculation
│
├── Fixed → fixed calculation
│
└── Tiered → tier calculation
This keeps method selection separate from the lower-level arithmetic.
Calculator Responsibilities¶
The calculator contains the low-level calculation functions.
These functions are intentionally independent of Frappe document operations.
For example, a percentage calculation can conceptually be represented as:
calculate_percentage(base_amount, rate)
↓
commission_amount
A tiered calculation similarly receives the relevant amount and tier configuration and returns the calculated result.
Keeping these functions focused makes them easier to test independently.
Calculation Result¶
After the calculation is complete, the engine has the information required to create a Commission Ledger entry.
A typical result contains information such as:
Sales Invoice
Sales Invoice Item
Commission Payee
Commission Rule
Commission Plan
Calculation Method
Base Amount
Rate
Fixed Amount
Commission Amount
Transaction Date
Company
Currency
The ledger layer persists this result.
Creating the Ledger Entry¶
The calculation engine does not treat the calculated amount as the final historical record by itself.
The result is passed to the ledger layer.
The ledger layer creates a Commission Ledger entry containing the calculation result and its source context.
Conceptually:
Calculation Result
│
▼
Ledger Creation
│
▼
Commission Ledger
Once created, the ledger entry becomes the historical representation of that commission calculation.
Idempotency¶
The ledger creation process uses a source_key.
The source key identifies the calculation source so that the same commission calculation is not inserted multiple times.
Conceptually:
Sales Invoice Event
│
▼
Calculate Commission
│
▼
Generate Source Key
│
▼
Already Exists?
/ \
Yes No
│ │
Skip Create Ledger
This protects the system against duplicate commission records when the same processing operation is encountered more than once.
Historical Snapshot¶
The calculation engine records the configuration used for the calculation in the ledger.
This is important because Commission Plans and Rules can change over time.
For example:
January
Rule = 5%
Commission = ₹500
↓
Rule changed
↓
March
Rule = 7%
Commission = ₹700
The January ledger entry remains ₹500.
The later rule change does not recalculate the historical January entry.
This makes the ledger an historical snapshot of what the engine calculated at the time of the transaction.
Reversal Calculations¶
Cancellation of a source Sales Invoice is handled as a separate ledger operation.
The original commission entry is not edited to change its amount.
Instead, the ledger layer creates a reversal entry.
Conceptually:
Original Commission
₹500
│
│ Sales Invoice cancelled
▼
Reversal
-₹500
The reversal entry references the original commission through reversal_of.
This preserves the original calculation while representing the financial effect of the cancellation separately.
Calculation and Statements¶
Commission Statements do not recalculate the commission amount.
Instead:
Sales Invoice
↓
Calculation Engine
↓
Commission Ledger
↓
Statement Resolver
↓
Commission Statement
The statement uses already-calculated ledger entries.
This means that statement generation does not depend on the current Commission Rule configuration.
It uses the historical results stored in the ledger.
Calculation and Reports¶
Reports also use the Commission Ledger as their historical source.
Commission Calculation
↓
Commission Ledger
├──→ Statements
└──→ Reports
This prevents reports from producing different results simply because a Commission Rule has changed since the original transaction.
Example: Complete Percentage Calculation¶
Consider a Sales Invoice containing:
Item: Smartphone
Quantity: 1
Rate: ₹10,000
The Sales Team contains:
Sales Person: Salesperson A
Contribution: 100%
The applicable configuration is:
Plan: Standard Sales Commission
Rule:
Rule Applies To: Item
Item: Smartphone
Calculation Method: Percentage
Rate: 5%
The calculation proceeds as follows.
1. Transaction Base¶
₹10,000 × 100%
= ₹10,000
2. Commission Rate¶
5%
3. Commission¶
₹10,000 × 5%
= ₹500
4. Ledger Result¶
Base Amount = ₹10,000
Rate = 5%
Commission Amount = ₹500
The result is stored as a Commission Ledger entry.
Example: Multiple Sales Team Members¶
Consider:
Invoice Item Amount = ₹20,000
Sales Team:
Sales Person A = 70%
Sales Person B = 30%
Commission Rule:
Rate = 5%
The engine processes the allocations independently.
For A:
Base = ₹20,000 × 70%
= ₹14,000
Commission = ₹14,000 × 5%
= ₹700
For B:
Base = ₹20,000 × 30%
= ₹6,000
Commission = ₹6,000 × 5%
= ₹300
The resulting commission records are:
Sales Person A → ₹700
Sales Person B → ₹300
The total commission is:
₹700 + ₹300 = ₹1,000
Testing the Calculation Layer¶
The low-level calculation functions are suitable for isolated tests because they do not need to perform Frappe document operations.
Calculation tests should cover:
- percentage calculations
- fixed amount calculations
- tiered calculations
- tier boundaries
- open-ended final tiers
- sales team allocation
- decimal arithmetic
- expected commission results
Higher-level tests can then verify that the resolved configuration is correctly connected to the calculator and ledger.
Debugging a Calculation¶
When a commission result is unexpected, inspect the calculation in this order:
1. Sales Invoice
↓
2. Sales Team contribution
↓
3. Commission Plan
↓
4. Commission Rule
↓
5. Commission Payee
↓
6. Base Amount
↓
7. Calculation Method
↓
8. Rate / Fixed Amount / Tiers
↓
9. Commission Ledger
This mirrors the actual processing pipeline and makes it easier to identify where an unexpected result originated.
Design Principles¶
The calculation engine follows several principles:
Resolve before calculating¶
The system first determines the applicable plan, rule, and payee before performing the mathematical calculation.
Keep mathematics isolated¶
Low-level calculation functions should not need to know about Frappe documents, workflows, or database operations.
Preserve transaction context¶
The calculation result retains enough information to understand where the commission came from.
Use Decimal arithmetic¶
Financial calculations use decimal arithmetic for predictable monetary behavior.
Make calculations reproducible¶
The ledger stores the historical calculation result so later configuration changes do not alter historical commissions.
Separate calculation from lifecycle processing¶
Calculation determines the commission amount. Statements, approvals, and payouts handle what happens to that commission afterward.
Development Entry Points¶
When working on the calculation engine, these are the most useful files to inspect:
| File | Start Here When |
|---|---|
commission_engine/transaction_builder.py |
Investigating transaction and Sales Team allocation |
commission_engine/resolver.py |
Investigating plan, rule, tier, or payee resolution |
commission_engine/evaluator.py |
Investigating the overall calculation flow |
commission_engine/engine.py |
Investigating calculation-method dispatch |
commission_engine/calculator.py |
Investigating mathematical calculation behavior |
commission_engine/ledger.py |
Investigating ledger creation, idempotency, or reversals |
Next Step¶
Continue with Events to understand how ERPNext Sales Invoice submission and cancellation trigger the commission engine.