Loan applications and repayment schedules
Before you start
You will build the contract side of a lender or a leasing company. An application is approved and becomes a contract. A Custom function works out the instalment so that the balance reaches a target at maturity, and writes one schedule row for each period. When the customer asks for a top-up, the same calculation runs again, the old schedule stays as a superseded version, and a late payment posts a penalty. Plan on a day. The calculation is the part to test carefully, with a contract whose answer you already know.
- Application
- Approval
- Contract
- Schedule solved
- Top-up recalculated
- Settlement
Build your loan rules with modules, workflows and a script step. Read and own the schedule script below.
What are we building?
Section titled “What are we building?”Six modules, four workflows and one script. The script is the centre of the recipe. Everything else is records and approvals you have seen in the other recipes.
Which modules and fields?
Section titled “Which modules and fields?”| Module | Key fields | Notes |
|---|---|---|
| Loan Products | Name, Payment style, Grace months, Late fee rate | Payment style is a lookup. Late fee rate is a percentage |
| Applications | Customer name, Product, Requested amount, Term months, Status | Status: Draft, In review, Approved, Rejected |
| Loan Contracts | Contract number, Product, Principal, Annual rate, Term months, First due date, Residual value, Schedule version, Status | Status: Active, Settled, Closed |
| Repayment Schedule | Contract, Version, Instalment no, Due date, Opening balance, Interest, Principal part, Payment, Closing balance, Late fee rate, Status | Status: Scheduled, Paid, Late, Superseded |
| Amendments | Contract, Type, Top-up amount, New term months, Status | Type: Top-up, Extension |
| Penalty Ledger | Contract, Schedule line, Amount, Reason, Waived | One row per charge, so the trail stays |
Link them with the Relation button: an application to a product, a contract to a product, a schedule row to a contract, an amendment to a contract, a penalty to a contract and to a schedule row. A relation field is named after the related module’s key field by default, so a schedule row’s contract field is loan_contracts_pr_id. You use that name in the script. Open the Fields page and read the exact system names before you copy anything. See Add a relation.
Give Loan Contracts a number with a default value script, as in An approvals hub, and turn on Table and Kanban views for Applications by Status.
How does an application become a contract?
Section titled “How does an application become a contract?”On Applications, create a workflow that runs when A record is created, After the record is saved (POST).
- Add Approval task, Title people will see
Approve application, Who decides? A role, Credit Committee. Stage In review. - On Rejected, add Record manager: Update, Status Rejected. Tick Add the rejection reason to the record.
- On Approved, add Record manager: Update, Status Approved.
- Add Record manager: Create, Loan Contracts. Fill Product, Principal (from Requested amount) and Term months with Insert data. Set Annual rate from the product, and Status Active.
A Create is a save like any other, so workflows on Loan Contracts run for it. This is how the next workflow can start from the contract.
How is the schedule solved?
Section titled “How is the schedule solved?”Create an Adhoc workflow on Loan Contracts named Calculate schedule. An adhoc workflow is started by a button or by another workflow.
- Add Load data: Repayment Schedule where Contract is this contract and Status is Scheduled. Name it
old_lines. - Add Record manager: Batch update, Repayment Schedule, over
old_lines. Set Status to Superseded. - Add Custom function with the script below.
- Add Record manager: Update, Loan Contracts (the record that started it). Set Schedule version to the old value plus 1.
Steps 1 and 2 keep the previous schedule. Rows marked Superseded stay for the auditor, and a table of Scheduled rows is the live one.
FunctionManager. and press Ctrl + Space to list the helpers you can call. The workflow editor offers the same list.The script finds the instalment by bisection. It tries a payment, simulates the balance month by month, and halves the range until the balance at maturity hits the residual value. Then it writes the rows.
var key = parseInt($cm_loan_contracts_base$.$loan_contracts_pr_id$, 10);var principal = parseFloat($cm_loan_contracts_base$.$principal$);var months = parseInt($cm_loan_contracts_base$.$term_months$, 10);var r = (parseFloat($cm_loan_contracts_base$.$annual_rate$) || 0) / 1200;var residual = parseFloat($cm_loan_contracts_base$.$residual_value$) || 0;var version = (parseInt($cm_loan_contracts_base$.$schedule_version$, 10) || 0) + 1;var d = '$cm_loan_contracts_base$.$first_due_date$'.split('/');
function endBalance(pmt) { var b = principal; for (var i = 0; i < months; i++) { b = b * (1 + r) - pmt; } return b;}var lo = 0, hi = principal * (1 + r);for (var k = 0; k < 60; k++) { var mid = (lo + hi) / 2; if (endBalance(mid) > residual) { lo = mid; } else { hi = mid; }}var pmt = (lo + hi) / 2;
var HashMap = Java.type('java.util.HashMap');var balance = principal;for (var n = 1; n <= months; n++) { var interest = balance * r; var closing = balance + interest - pmt; var due = new Date(parseInt(d[2], 10), parseInt(d[1], 10) - 1 + (n - 1), parseInt(d[0], 10)); var row = new HashMap(); row.put('loan_contracts_pr_id', String(key)); row.put('version', String(version)); row.put('instalment_no', String(n)); row.put('due_date', due.getFullYear() + '-' + ('0' + (due.getMonth() + 1)).slice(-2) + '-' + ('0' + due.getDate()).slice(-2)); row.put('opening_balance', balance.toFixed(2)); row.put('interest', interest.toFixed(2)); row.put('principal_part', (pmt - interest).toFixed(2)); row.put('payment', pmt.toFixed(2)); row.put('closing_balance', closing.toFixed(2)); row.put('status', '1'); FunctionManager.createRecords('cm_repayment_schedule_base', row); balance = closing;}Read it in four pieces. The first block turns tokens into numbers, with || 0 so an empty field means zero. The endBalance function simulates the loan. The for (var k...) loop is the goal seek: 60 halvings are far more than money needs. The last loop writes one row each, with status 1 for the first value of the Schedule statuses lookup. Insert each token with Insert data, so the module table name in the token matches yours. createRecords takes the module’s table name too. The Modules page shows it on each card, and you can copy it.
Dates are written as text. The master accepts yyyy-MM-dd, which is what the script produces. The first due date arrives in the day, month, year order, so the script splits it on /. Test with one contract before you trust it, and start with a due day of 1 to 28, because a day such as the 31st moves into the next month in months that are shorter. See Custom function and Write a custom function step.
Each createRecords call is a normal save. Workflows on Repayment Schedule run for every row. Keep that module free of workflows that you do not want 36 times.
Add a Smart view button Calculate schedule to the Loan Contracts record page that runs this workflow, and a related tab that lists the Scheduled rows.
How does a top-up recalculate the schedule?
Section titled “How does a top-up recalculate the schedule?”On Amendments, create a workflow that runs when A record is created, with an approval first if your policy needs one.
- After the approval’s Approved path, add Record manager: Update, Loan Contracts, with the amendment’s Contract as the Record ID. Set Principal to
parseFloat($cm_loan_contracts_base$.$principal$) + parseFloat($cm_amendments_base$.$topup_amount$). The key is needed, because the amendment is a different module. - Add Custom function:
FunctionManager.runAdhocWorkflow('cm_loan_contracts_base', '$cm_amendments_base$.$loan_contracts_pr_id$', 'WORKFLOW ID');Replace WORKFLOW ID with the number of Calculate schedule, which you can read from its address in the Designer. The call runs the adhoc workflow on that contract straight away and returns a result object. Check the returned result in your script before continuing. Because the top-up changed the principal first, the new schedule uses the new amount, and the old rows are already marked Superseded.
For a partial disbursement, add a Disbursed amount field and use it in place of Principal in the script. The recalculation is the same.
How are penalties and settlement handled?
Section titled “How are penalties and settlement handled?”Late payment. Create a workflow on Repayment Schedule, Adhoc, Also run on a schedule, Every day at 09:00.
- Load data: Repayment Schedule where Status is Scheduled and Due date is before today. Use a Smart Script that returns today’s date for the date rule. Name it
late. - Loop, Each record in a list,
late. - Inside, Record manager: Create, Penalty Ledger. Amount is
parseFloat($late$.$payment$) * parseFloat($late$.$late_fee_rate$) / 100. - Inside, Record manager: Update the schedule row, Status Late.
The row’s Late fee rate is a field the script copies from the product when it creates rows. Add a row.put for it.
Early settlement. An Adhoc workflow on Loan Contracts: load the rows that are still Scheduled, then a Record manager Batch update sets them to Superseded, and the contract’s Status moves to Settled. The payoff is the sum of their Principal part, and FunctionManager.evaluateSumFunction adds a column across rows that match a clause. Read the system names on the Fields page when you write the clause. Add a Smart document step if the customer needs a settlement letter.
Who should see what?
Section titled “Who should see what?”Credit staff create and edit Applications. A credit committee role gets the approval tasks. Operations edit Loan Contracts and read Repayment Schedule. A finance profile may edit Penalty Ledger to waive a charge. Keep Repayment Schedule read-only on the form for everyone, so the script owns it.
What can I change?
Section titled “What can I change?”- Sharia-compliant financing. Financing through Tawarruq works by integrating with the Tawarruq engine. The application, approval and contract parts of this recipe stay the same.
- Different payment styles. Add a Payment style lookup value and a Decision in the schedule workflow, one Custom function per style.
- Post to accounting. A REST API or Integration step sends each approved contract on. See Talking to any system.
- Customer history. A Smart view shows the contract’s schedule versions on a tab.
Where can I read the story?
Section titled “Where can I read the story?”The Solutions page follows one lending contract from product setup to settlement: Lending and financing.
Check yourself
What's next
Last checked