Skip to main content

Loan Rates

A Loan Rate (crm.bank.rate) represents one specific financing offer a bank makes — a tuple of (term, interest, rate type, minimum down payment). Each row drives one entry in the BPO Quotation Calculator's Loan Term dropdown. Like banks, loan rates are synced from the distributor and not manually maintained on the BPO side.

:::tip One bank, many rate rows A bank typically offers multiple loan options — e.g. 12, 24, 36, 48, and 60-month terms, each with their own interest rate and minimum down payment. Each option is its own crm.bank.rate row. Agents pick a row in the calculator; the row's rate type drives whether the math is add-on or amortizing. :::

What's in a rate row

FieldTypeNotes
BankMany2one → crm.bankThe parent bank. Required, ondelete=cascade.
BrandMany2one → crm.brandRelated stored from bank_id.brand_id. Cheap filter for grouping.
Display NameCharComputed: `Bank — N mo @ X.XX% (Add-On
Term (months)IntegerRequired, > 0
Annual Interest %FloatRequired, ≥ 0. The headline rate the bank advertises (e.g. 6.5 for 6.5%).
Rate TypeSelectionadd_on or effective. Defines the monthly amortization formula.
Min Down Payment %FloatThe bank's minimum down payment as a percentage of the vehicle SRP. Drives the soft DP warning on the quotation calculator.
SequenceIntegerOrder in the calculator's Loan Term dropdown
ActiveBooleanMirrors the distributor's active flag
NotesTextFree text from the distributor
Distributor Rate IDIntegerRemote id, used as the sync upsert key

Constraints:

  • term_months > 0
  • interest_rate ≥ 0
  • 0 ≤ min_dp_pct ≤ 100
  • Unique on (bank_id, term_months, rate_type) — a bank cannot have two rows with the same term and rate type

Add-on vs effective rate type

This is the most important field on a rate row because it determines how the calculator computes the monthly amortization. Different banks quote differently and the dealer financing market in the Philippines uses both.

Add-on rate

total_interest = principal × (annual% / 100) × years
monthly = (principal + total_interest) / months

The bank computes the total interest once at the start as a flat percentage of the principal multiplied by the loan term in years, then divides the principal + interest evenly across the months. This is the conventional "add-on" or "flat" rate quoted by most Philippine dealer financing programs.

Example: ₱960,000 principal, 6.5% annual, 4 years (48 months)

  • Total interest = 960,000 × 0.065 × 4 = ₱249,600
  • Monthly = (960,000 + 249,600) / 48 = ₱25,200

Effective rate

r = (annual% / 100) / 12
factor = (1 + r) ^ months
monthly = principal × (r × factor) / (factor − 1)

Standard amortizing PMT formula. The customer pays back interest on the declining balance, not on the full principal for the full term. Same advertised rate yields a lower monthly amortization than add-on.

Example: Same numbers

  • r = 0.065 / 12 ≈ 0.005417
  • factor = (1.005417)^48 ≈ 1.2961
  • Monthly ≈ ₱22,729
  • Total interest = monthly × 48 − 960,000₱131,000

The same vehicle, same down payment, same advertised "6.5%" — but the customer pays roughly ₱118,000 more under the add-on convention. This is why agents need to pick the right rate_type per bank, and why the rate sheet on the distributor side stores the convention explicitly.

If the annual rate is zero, the effective formula falls back to monthly = principal / months (interest-free instalment plan).

Where rate rows are used

Rate rows are pulled into a single dropdown on the lead Quotation tab when the agent picks a bank:

Loan Term dropdown on the quotation tab

The dropdown is filtered to active rows of the chosen bank only. The display name format [BANK] — 48 mo @ 6.50% (Add-On) makes the term + rate type immediately readable.

When a row is picked:

  1. crm.lead.quotation_term_months, quotation_interest_rate, quotation_rate_type, quotation_min_dp_pct are populated as related stored fields
  2. The financing computation triggers — see Quotation Calculator → Add-On vs Effective rate math
  3. The DP warning may appear if the entered down payment is below this row's min_dp_pct

Open the Loan Rates list

Navigate to BPO CRM → Data → Loan Rates.

Loan Rates flat list

The default view groups by Bank and shows every active rate row across every brand. Useful for:

  • Bulk reviewing the rate sheet for a quarter-end audit
  • Spotting banks with unusually high or low rates compared to peers
  • Counting how many term options each bank offers
ColumnDescription
BrandThe brand whose distributor pushed this row
BankThe parent crm.bank
Term (months)Loan term
Annual Interest %Headline rate
Rate TypeAdd-On or Effective
Min DP %Bank's minimum down payment
Active

The search bar exposes filters for Active, Add-On, and Effective, plus group-by Brand or Bank.

Sync paths

Identical to banks — see Banks → Sync paths:

  1. Scheduled cron (every 6 hours, all brands)
  2. Sync Banks button on the brand form (one brand)
  3. Update Bank Rates button on the lead Quotation tab (all brands, agent-triggered via sudo)

All three paths upsert by (bank_id, term_months, rate_type) on first run, then by distributor_rate_id on subsequent runs. Existing rows are updated in place; missing-on-distributor rows are not deleted (they're left in place locally so historical lead references still resolve).

Access matrix

GroupReadSyncEdit LocallyDelete
BPO Agent✅ (Update Bank Rates)
BPO Supervisor
BPO Manager

Same restrictions as banks. Local edits are overwritten on the next sync.

Audit trail

Each rate row carries create_uid, create_date, write_uid, write_date. After the first sync, these are usually OdooBot (the cron runs as superuser).

When an agent picks a rate row in the calculator, the lead's quotation_bank_rate_id is a tracked field — every change appears in the lead's chatter with the timestamp and user. After endorse, this field is locked (see Quotation Calculator → Lock on endorse) so the rate the dealer ultimately receives is provably the rate the customer agreed to.

For a closed sale, an auditor can reconstruct the financing terms by:

  1. Open the won lead
  2. Look at the Quotation tab — the financing block is read-only and shows the snapshot at endorsement time
  3. Look at the Endorsement tab — the dealer-side dealer.crm.lead link contains the same financing snapshot under bpo_quotation_* fields
  4. The customer's emailed PDF (attached to the lead's chatter via Email Quotation) is the third source of truth

Typical audit questions

"How many leads were quoted at the highest rate this quarter?"

  • Pipeline → All Leads → Filter quotation_interest_rate >= X and date_endorsed in the quarter range. Group by Bank.

"Did the dealer-side SO match the BPO quotation rate?"

  • Open the BPO lead's Endorsement tab → click the dealer lead link → BPO Origin tab → bpo_quotation_interest_rate field. Compare against the BPO side's quotation_interest_rate. They must match — both are populated by the same payload at endorse time.

"Who changed the rate after the customer received the PDF?"

  • The BPO lead's chatter shows every change to quotation_bank_rate_id with the user and timestamp. After endorse, no further changes are possible (the lock raises immediately).

Retention

Rate rows are never auto-deleted. Sync upserts in place — historical leads keep their quotation_bank_rate_id reference even if the rate row is later updated. Old quotes show the rate as it was at quotation time because the snapshot fields (quotation_interest_rate, quotation_rate_type, etc.) are stored, not just related.

Troubleshooting

SymptomLikely cause
Loan Term dropdown empty after picking a bankThe bank has no crm.bank.rate rows. Distributor admin needs to add at least one row.
Rate row appears under the wrong brandThe bank itself is under the wrong brand. Check bank_id.brand_id — fix on the distributor side, then re-sync.
Monthly amortization seems off vs the bank's published tableRate type mismatch — verify the rate row's rate_type matches what the bank actually quotes. Add-on vs effective produce very different monthlies for the same advertised %.
Two rate rows for the same bank/term/typeShould be impossible — the unique constraint blocks it. If you see it, check whether one is archived (active=False).
Sync brings down 0 rates for a bankThe distributor's cheryapp.bank.rate model isn't accessible (older branch) or the bank really has no rates yet. The sync logs a warning and continues.
Min DP % is 0 on every rowThe distributor's rate sheet didn't populate it. Default is 20%. Ask the distributor admin to fill in the column.