feat: store payment installments in a transactions collection

Move embedded payment rows into Transaction documents with due dates so remaining tuition can be tracked separately from paid amounts.
This commit is contained in:
2026-08-16 07:32:27 +03:30
parent b4ca5678bf
commit 06efcd5b90
7 changed files with 341 additions and 53 deletions
+40 -1
View File
@@ -6,7 +6,11 @@ const {
getPayableAmount,
normalizeDiscount,
sanitizeNotes,
NOTES_MAX_LENGTH
NOTES_MAX_LENGTH,
rialsToToman,
isPaidTransaction,
sumPaidTransactions,
remainingPayable
} = require('./paymentAmount');
describe('payment amount helpers', () => {
@@ -36,6 +40,41 @@ describe('payment amount helpers', () => {
});
});
describe('rialsToToman', () => {
it('drops one zero so spreadsheet Rials become website Toman', () => {
assert.equal(rialsToToman(110_000_000), 11_000_000);
assert.equal(rialsToToman(5_000_000), 500_000);
assert.equal(rialsToToman(0), 0);
});
});
describe('paid vs pending transactions', () => {
it('counts only paid transactions toward the paid total', () => {
const transactions = [
{ amount: 8_000_000, status: 'paid', date: '2026-08-03' },
{ amount: 3_000_000, status: 'pending', dueDate: '2026-08-23' }
];
assert.equal(sumPaidTransactions(transactions), 8_000_000);
assert.equal(
remainingPayable({ amount: 11_000_000, discount: 0 }, transactions),
3_000_000
);
});
it('treats a 10m billed course as 1m off an 11m tuition', () => {
assert.equal(getPayableAmount({ amount: 11_000_000, discount: 1_000_000 }), 10_000_000);
});
it('treats an 8m billed course as 3m off an 11m tuition', () => {
assert.equal(getPayableAmount({ amount: 11_000_000, discount: 3_000_000 }), 8_000_000);
});
it('does not treat pending remainder rows as paid', () => {
assert.equal(isPaidTransaction({ status: 'pending', amount: 2_000_000 }), false);
assert.equal(isPaidTransaction({ status: 'paid', amount: 8_000_000 }), true);
});
});
describe('sanitizeNotes', () => {
it('trims notes and caps length', () => {
assert.equal(sanitizeNotes(' hello '), 'hello');