Skip to content

MON · Money

Everything a driver owes is a charges row. A payment is recorded with its proof and spread over charges by payment_allocations; rebates, refunds, waivers and corrections are charge_adjustments. Paid, balance and overdue are always computed. Money is voided, never deleted, and has no pool: it follows the driver. Payouts settle rent with rental companies. Code MON.

These questions are open in #86. Each gets a rule with a new ID once it is answered.

  • MON open 1 · Refunded, waived and partial adjustments
  • MON open 3 · Charge types that need a rental
  • MON open 4 · Payout totals
  • MON open 5 · Repo fine
  • Due-date rules per charge type: OQ11 (#37)
  • v1's balance of record: OQ19 (#43)
  • DOC-Q10, MON open 2 (a cash payment, or a charge with no legal entity): never cross-entity. A new rule, R-MON-38.
  • charge.status_locked had no rule of its own: R-MON-37 is added for it.

R-MON-01 · A charge, payment, allocation or adjustment amount is greater than zero

Section titled “R-MON-01 · A charge, payment, allocation or adjustment amount is greater than zero”
  • Status: planned
  • Example: given a new payment, when its amount is saved as 0, then it is refused; a refund is an adjustment with a positive amount, never a negative charge.
  • Refusal: none: check_violation (23514).
  • Who: every writer.
  • Source: [Ref] Database rules and conventions, Money; [Ref] Data model V13.

R-MON-02 · Charges, payments, allocations and adjustments are never deleted

Section titled “R-MON-02 · Charges, payments, allocations and adjustments are never deleted”
  • Status: planned
  • Example: given a payment recorded by mistake, when a super_admin deletes it, then nothing is deleted; it is voided instead.
  • Refusal: none: no effect, as there is no delete policy.
  • Who: every app user.
  • Source: D9 (P13); [Ref] Roles, policy matrix (never: void instead).

R-MON-03 · A rental has at most one rent charge per billing day

Section titled “R-MON-03 · A rental has at most one rent charge per billing day”
  • Status: planned
  • Example: given the rent job billed a rental for 2026-10-07, when it runs again for the same day, then the second charge is refused.
  • Refusal: none: unique_violation (23505), from charges_one_rent_per_day.
  • Who: every writer, the rent job included.
  • Source: [Ref] Database rules and conventions, Money (no double billing).

R-MON-04 · A charge with a billing day belongs to a rental

Section titled “R-MON-04 · A charge with a billing day belongs to a rental”
  • Status: planned
  • Example: given a charge with billing_day 2026-10-07, when it is saved with no rental, then it is refused.
  • Refusal: none: check_violation (23514).
  • Who: every writer.
  • Source: [Ref] Data model V13: charges.

R-MON-05 · A rent charge's margin is its amount minus its company amount, computed by the database

Section titled “R-MON-05 · A rent charge's margin is its amount minus its company amount, computed by the database”
  • Status: planned
  • Example: given a rent charge of Rp 150.000 with company_amount Rp 110.000, when it is read, then margin is 40000; a charge with no company_amount has no margin.
  • Refusal: none: margin is generated and can't be written.
  • Who: every charge.
  • Source: [Ref] Database rules and conventions, Money; [Ref] Data model V13: charges.margin.

R-MON-06 · A charge's balance is its amount, less allocations from payments that aren't voided, less its adjustments

Section titled “R-MON-06 · A charge's balance is its amount, less allocations from payments that aren't voided, less its adjustments”
  • Status: planned
  • Example: given a charge of Rp 300.000, Rp 100.000 allocated from a valid payment, Rp 50.000 from a voided one and a Rp 20.000 rebate, when its balance is read, then it is 180000.
  • Refusal: none.
  • Who: app.charge_balance and v_charge_balance.
  • Source: [Ref] Database rules and conventions, Money.

R-MON-07 · A driver's open balance is the sum of their charges' balances

Section titled “R-MON-07 · A driver's open balance is the sum of their charges' balances”
  • Status: planned
  • Example: given a driver with charges whose balances are Rp 150.000, Rp 0 and Rp 75.000, when v_driver_balance is read, then the open balance is 225000.
  • Refusal: none.
  • Who: v_driver_balance.
  • Source: [Ref] Database rules and conventions, Views; the M0 acceptance checks.

R-MON-08 · A charge is overdue when a balance remains after its due date plus overdue_limit_days, in the pool's date

Section titled “R-MON-08 · A charge is overdue when a balance remains after its due date plus overdue_limit_days, in the pool's date”
  • Status: planned
  • Example: given overdue_limit_days is 3 and a charge due on 2026-10-01 with Rp 50.000 left, when v_driver_balance is read on 2026-10-04, then it isn't overdue; on 2026-10-05 it is.
  • Refusal: none.
  • Who: v_driver_balance, the gate and collection.
  • Source: [Ref] Database rules and conventions, Money; D9 (P11).

R-MON-09 · A charge's status is recomputed after every change to its allocations or adjustments, and after a void of a payment that paid it

Section titled “R-MON-09 · A charge's status is recomputed after every change to its allocations or adjustments, and after a void of a payment that paid it”
  • Status: planned
  • Example: given an open charge, when a payment is allocated to it in full, then its status reads paid without anyone setting it.
  • Refusal: none.
  • Who: app.refresh_charge_status, called by the money triggers.
  • Source: [Ref] Database rules and conventions, Money (charge status is computed).

R-MON-10 · A charge with nothing allocated or adjusted is open

Section titled “R-MON-10 · A charge with nothing allocated or adjusted is open”
  • Status: planned
  • Example: given a new damage charge of Rp 300.000, when it is read, then its status is open.
  • Refusal: none.
  • Who: every charge.
  • Source: [Ref] Database rules and conventions, Money.

R-MON-11 · A charge partly covered by allocations is partially_paid

Section titled “R-MON-11 · A charge partly covered by allocations is partially_paid”
  • Status: planned
  • Example: given a charge of Rp 300.000, when Rp 100.000 is allocated to it, then its status is partially_paid.
  • Refusal: none.
  • Who: every charge.
  • Source: [Ref] Database rules and conventions, Money; the M0 acceptance checks.

R-MON-12 · A charge whose balance reaches zero through allocations is paid

Section titled “R-MON-12 · A charge whose balance reaches zero through allocations is paid”
  • Status: planned
  • Example: given a charge of Rp 300.000 with Rp 100.000 allocated, when Rp 200.000 more is allocated, then its status is paid.
  • Refusal: none.
  • Who: every charge.
  • Source: [Ref] Database rules and conventions, Money.

R-MON-13 · An allocation can't use a voided payment

Section titled “R-MON-13 · An allocation can't use a voided payment”
  • Status: planned
  • Example: given a voided payment, when it is allocated to an open charge, then it is refused with the detail {"payment_id": "…"}.
  • Refusal: allocation.payment_voided
  • Who: every writer.
  • Source: [Ref] Database rules and conventions, Money (allocation guard).

R-MON-14 · A payment's allocations can't add up to more than the payment

Section titled “R-MON-14 · A payment's allocations can't add up to more than the payment”
  • Status: planned
  • Example: given a payment of Rp 200.000 with Rp 150.000 already allocated, when Rp 100.000 more is allocated from it, then it is refused.
  • Refusal: allocation.exceeds_payment
  • Who: every writer.
  • Source: [Ref] Database rules and conventions, Money (allocation guard).

R-MON-15 · An allocation can't exceed the charge's open balance

Section titled “R-MON-15 · An allocation can't exceed the charge's open balance”
  • Status: planned
  • Example: given a charge with Rp 150.000 open, when Rp 200.000 is allocated to it, then it is refused with the detail {"charge_id": "…", "balance": 150000, "amount": 200000}.
  • Refusal: allocation.exceeds_balance
  • Who: every writer.
  • Source: [Ref] Database rules and conventions, Money (allocation guard); the M0 acceptance checks.

R-MON-16 · A payment is allocated to the same charge at most once

Section titled “R-MON-16 · A payment is allocated to the same charge at most once”
  • Status: planned
  • Example: given a payment allocated to a charge, when a second allocation of that payment to that charge is saved, then it is refused; the first allocation's amount is what changes.
  • Refusal: none: unique_violation (23505).
  • Who: every writer.
  • Source: [Ref] Data model V13: payment_allocations.
Section titled “R-MON-17 · An allocation is flagged cross_entity when the payment's bank account belongs to a different legal entity than the charge”
  • Status: planned
  • Example: given a TOP rent charge and a transfer into MOP's account, when the payment is allocated to the charge, then the allocation reads cross_entity true.
  • Refusal: none: the flag is set, not refused.
  • Who: every writer.
  • Source: D9 (Q7); the M0 acceptance checks.

R-MON-18 · When allow_cross_entity_payments is off, a cross-entity allocation is refused

Section titled “R-MON-18 · When allow_cross_entity_payments is off, a cross-entity allocation is refused”
  • Status: planned
  • Example: given allow_cross_entity_payments is false, when a MOP payment is allocated to a TOP charge, then it is refused with the detail {"payment_entity_id": "…", "charge_entity_id": "…"}.
  • Refusal: allocation.cross_entity_blocked
  • Who: every writer.
  • Source: D9 (Q7); the M0 acceptance checks.
Section titled “R-MON-19 · A charge on a rental takes the legal entity of the rental's contract”
  • Status: planned
  • Example: given a rental whose contract is signed by MOP, when its rent is billed, then each rent charge's legal entity is MOP.
  • Refusal: none.
  • Who: every function that raises a charge on a rental.
  • Source: D9 (Q7).

R-MON-20 · Voiding a payment reopens every charge it paid

Section titled “R-MON-20 · Voiding a payment reopens every charge it paid”
  • Status: planned
  • Example: given a payment of Rp 300.000 that paid two charges in full, when it is voided, then both charges' balances come back and their status is recomputed to open.
  • Refusal: none.
  • Who: every writer.
  • Source: [Ref] Database rules and conventions, Money (void, never delete); the M0 acceptance checks.

R-MON-21 · A voided payment records who voided it, when, and why

Section titled “R-MON-21 · A voided payment records who voided it, when, and why”
  • Status: planned
  • Example: given a payment, when voided_at is set with no void_reason, then it is refused.
  • Refusal: none: check_violation (23514).
  • Who: every writer.
  • Source: [Ref] Data model V13: payments.

R-MON-22 · Every payment has a proof file

Section titled “R-MON-22 · Every payment has a proof file”
  • Status: planned
  • Example: given a new cash payment, when it is saved with no proof_path, then it is refused.
  • Refusal: none: not_null_violation (23502).
  • Who: every writer.
  • Source: [Ref] Data model V13: payments (proof is required).

R-MON-23 · A non-cash payment names the company bank account it was paid into

Section titled “R-MON-23 · A non-cash payment names the company bank account it was paid into”
  • Status: planned
  • Example: given a QRIS payment, when it is saved with no bank account, then it is refused; a cash payment needs none.
  • Refusal: none: check_violation (23514).
  • Who: every writer.
  • Source: [Ref] Data model V13: payments.

R-MON-24 · The admin fee is one charge on the driver, raised at registration, with no rental

Section titled “R-MON-24 · The admin fee is one charge on the driver, raised at registration, with no rental”
  • Status: planned
  • Example: given a walk-in registered as a lead, when registration completes, then he has one admin fee charge and it has no rental.
  • Refusal: none.
  • Who: the registration function (M3).
  • Source: D9 (Q1).

R-MON-25 · The admin fee is priced from the pricing group of the driver's preferred model

Section titled “R-MON-25 · The admin fee is priced from the pricing group of the driver's preferred model”
  • Status: planned
  • Example: given a lead whose preferred model is in a pricing group with an admin fee of Rp 200.000, when he registers, then his admin fee charge is Rp 200.000.
  • Refusal: none.
  • Who: the registration function (M3).
  • Source: D9 (Q1).
Section titled “R-MON-26 · The admin fee charge gets its legal entity at booking”
  • Status: planned
  • Example: given a driver's admin fee charge with no legal entity, when he is booked on a contract signed by TOP, then the charge's legal entity is TOP.
  • Refusal: none.
  • Who: the booking function (M4).
  • Source: D9 (Q7).

R-MON-27 · A driver's charges and payments are seen at the driver's current pool, so they move with the driver on a transfer

Section titled “R-MON-27 · A driver's charges and payments are seen at the driver's current pool, so they move with the driver on a transfer”
  • Status: planned
  • Example: given a driver moved from PML to SBY with Rp 200.000 still owed, when a finance user scoped to SBY reads charges, then the old debt comes back; one scoped to PML no longer sees it.
  • Refusal: none: hidden at other pools.
  • Who: every member.
  • Source: D9 (Q2).

R-MON-28 · An adjustment states its reason

Section titled “R-MON-28 · An adjustment states its reason”
  • Status: planned
  • Example: given a waiver, when it is saved with no reason, then it is refused.
  • Refusal: none: not_null_violation (23502).
  • Who: every writer.
  • Source: [Ref] Data model V13: charge_adjustments.

R-MON-29 · Reading charges, payments, allocations and adjustments needs payment:read at the driver's pool

Section titled “R-MON-29 · Reading charges, payments, allocations and adjustments needs payment:read at the driver's pool”
  • Status: planned
  • Example: given a checker at PML, when she reads payments, then nothing comes back; a finance user at PML sees them.
  • Refusal: none: hidden.
  • Who: super_admin, admin, finance and viewer hold payment:read.
  • Source: [Ref] Roles, policy matrix.

R-MON-30 · Recording a charge, a payment or an allocation needs payment:write at the driver's pool

Section titled “R-MON-30 · Recording a charge, a payment or an allocation needs payment:write at the driver's pool”
  • Status: planned
  • Example: given a viewer at PML, when he records a payment, then it is refused; a finance user at PML may.
  • Refusal: none: insufficient_privilege (42501).
  • Who: super_admin, admin and finance hold payment:write.
  • Source: [Ref] Roles, policy matrix.

R-MON-31 · Voiding a payment or adding an adjustment needs payment:approve at the driver's pool

Section titled “R-MON-31 · Voiding a payment or adding an adjustment needs payment:approve at the driver's pool”
  • Status: planned
  • Example: given a user with payment:write only, through a grant override, when he voids a payment, then nothing changes; a finance user, who holds payment:approve, may.
  • Refusal: none: insufficient_privilege (42501) on insert; no effect on update.
  • Who: super_admin, admin and finance hold payment:approve.
  • Source: [Ref] Roles, policy matrix.

R-MON-32 · A rental company is paid per rented day, through the rent charges linked to its payout

Section titled “R-MON-32 · A rental company is paid per rented day, through the rent charges linked to its payout”
  • Status: planned
  • Example: given a car rented for 20 days of October and in maintenance for 11, when its rental company's October payout is built, then it covers the 20 rent charges and nothing for the other days.
  • Refusal: none.
  • Who: the payout function (M6).
  • Source: D9 (Q4).

R-MON-33 · A paid payout records when it was paid

Section titled “R-MON-33 · A paid payout records when it was paid”
  • Status: planned
  • Example: given a payout, when its status is set to paid with no paid_at, then it is refused.
  • Refusal: none: check_violation (23514).
  • Who: every writer.
  • Source: [Ref] Data model V13: payouts.

R-MON-34 · Reading payouts needs payment:read at any pool of the organization

Section titled “R-MON-34 · Reading payouts needs payment:read at any pool of the organization”
  • Status: planned
  • Example: given a finance user scoped to PML only, when she reads payouts, then the organization's payouts come back.
  • Refusal: none: hidden without the permission.
  • Who: super_admin, admin, finance and viewer hold payment:read.
  • Source: [Ref] Roles, policy matrix (rental companies, payouts).

R-MON-35 · Creating or changing a payout needs payment:approve

Section titled “R-MON-35 · Creating or changing a payout needs payment:approve”
  • Status: planned
  • Example: given a viewer, when he approves a payout, then nothing changes; a finance user may.
  • Refusal: none: insufficient_privilege (42501) on insert; no effect on update.
  • Who: super_admin, admin and finance hold payment:approve.
  • Source: [Ref] Roles, policy matrix.

R-MON-36 · The daily rent job bills every active rental once per billing day

Section titled “R-MON-36 · The daily rent job bills every active rental once per billing day”
  • Status: planned
  • Example: given 40 active rentals, when the rent job runs for 2026-10-07, then 40 rent charges are raised for that day, and a second run raises none.
  • Refusal: none.
  • Who: the rent job (M6).
  • Source: [Ref] Database rules and conventions, Scheduled jobs.

R-MON-37 · App users can't set a charge's status directly; only the database computes it

Section titled “R-MON-37 · App users can't set a charge's status directly; only the database computes it”
  • Status: planned
  • Example: given an open charge, when a finance user updates its status to paid, then it is refused and the status stays open.
  • Refusal: charge.status_locked
  • Who: every app user (authenticated).
  • Source: [Ref] Database rules and conventions, Money (charge status is computed: nobody types it in). Added because pack B raises charge.status_locked.
Section titled “R-MON-38 · A cash payment, or a charge with no legal entity, is never cross-entity”
  • Status: planned
  • Example: given an admin fee charge with no legal entity yet, when a transfer into MOP's account is allocated to it, then cross_entity is false; a cash payment allocated to a TOP charge is false too.
  • Refusal: none.
  • Who: every writer.
  • Source: D9 (Q7: the flag compares the payment's bank account's legal entity with the charge's, so without both there is nothing to compare). Added for DOC-Q10 (MON open 2).