'use strict'; const { describe, it } = require('node:test'); const assert = require('node:assert/strict'); const { calculateSessionDurationHours, resolveSessionDurationHours, calculatePercentageShare, calculateHourlyShare, calculateExtraExpenses, calculateProfessorPayout, calculateNetProfit, calculatePendingReceivables, calculateOverpaidAmount, calculateStudentRevenuePerSession } = require('./professorShare'); describe('calculateSessionDurationHours', () => { it('computes the duration between two clock times', () => { assert.equal(calculateSessionDurationHours('18:00', '20:00'), 2); assert.equal(calculateSessionDurationHours('9:30', '11:00'), 1.5); }); it('returns 0 for missing or invalid input', () => { assert.equal(calculateSessionDurationHours('', ''), 0); assert.equal(calculateSessionDurationHours(null, undefined), 0); assert.equal(calculateSessionDurationHours('25:00', '26:00'), 0); }); it('handles sessions that cross midnight', () => { assert.equal(calculateSessionDurationHours('23:00', '01:00'), 2); }); }); describe('resolveSessionDurationHours', () => { it('prefers the course hoursPerSection when present', () => { assert.equal(resolveSessionDurationHours({ course: { hoursPerSection: 2 }, startTime: '18:00', endTime: '20:30' }), 2); }); it('falls back to the class start/end time window', () => { assert.equal(resolveSessionDurationHours({ course: null, startTime: '18:00', endTime: '20:00' }), 2); assert.equal(resolveSessionDurationHours({ course: { hoursPerSection: 0 }, startTime: '18:00', endTime: '19:30' }), 1.5); }); it('returns 0 when nothing is configured', () => { assert.equal(resolveSessionDurationHours({}), 0); }); }); describe('calculatePercentageShare (Model A)', () => { it('applies the percentage to revenue', () => { assert.equal(calculatePercentageShare({ payoutPercentage: 40, revenue: 10_000_000 }), 4_000_000); }); it('deducts serviceFeePerPerson * studentsCount from revenue before calculating percentage', () => { // 2 students with 10M tuition (revenue = 20M), serviceFeePerPerson = 400,000, 50% payout // Net revenue = 20M - (400,000 * 2) = 19,200,000 // Professor share = 19,200,000 * 50% = 9,600,000 assert.equal( calculatePercentageShare({ payoutPercentage: 50, revenue: 20_000_000, serviceFeePerPerson: 400_000, studentsCount: 2 }), 9_600_000 ); }); it('clamps percentage above 100 and negative revenue to zero', () => { assert.equal(calculatePercentageShare({ payoutPercentage: 150, revenue: 1_000_000 }), 1_000_000); assert.equal(calculatePercentageShare({ payoutPercentage: 40, revenue: -500 }), 0); }); it('returns 0 when there is no revenue', () => { assert.equal(calculatePercentageShare({ payoutPercentage: 40, revenue: 0 }), 0); }); }); describe('calculateHourlyShare (Model B)', () => { it('multiplies rate * duration * sessions', () => { // 200,000 Toman/hour * 2 hours * 10 sessions = 4,000,000 assert.equal( calculateHourlyShare({ payoutHourlyRate: 200_000, sessionDurationHours: 2, sessionsCount: 10 }), 4_000_000 ); }); it('handles zero sessions held (no payout yet)', () => { assert.equal( calculateHourlyShare({ payoutHourlyRate: 200_000, sessionDurationHours: 2, sessionsCount: 0 }), 0 ); }); it('treats negative or missing inputs as zero', () => { assert.equal(calculateHourlyShare({}), 0); assert.equal(calculateHourlyShare({ payoutHourlyRate: -100, sessionDurationHours: 2, sessionsCount: 5 }), 0); }); }); describe('calculateExtraExpenses', () => { it('multiplies the flat per-session allowance by sessions held', () => { assert.equal(calculateExtraExpenses({ extraExpensePerSession: 50_000, sessionsCount: 8 }), 400_000); }); it('defaults to 0 when not applicable', () => { assert.equal(calculateExtraExpenses({}), 0); assert.equal(calculateExtraExpenses({ extraExpensePerSession: 0, sessionsCount: 12 }), 0); }); }); describe('calculateProfessorPayout', () => { it('combines base share (percentage) with extra expenses', () => { const result = calculateProfessorPayout({ payoutType: 'percentage', payoutPercentage: 40, revenue: 10_000_000, extraExpensePerSession: 100_000, sessionsCount: 5 }); assert.equal(result.baseShare, 4_000_000); assert.equal(result.extraExpenses, 500_000); assert.equal(result.totalPayout, 4_500_000); }); it('combines base share (hourly) with extra expenses', () => { const result = calculateProfessorPayout({ payoutType: 'hourly', payoutHourlyRate: 300_000, sessionDurationHours: 1.5, extraExpensePerSession: 80_000, sessionsCount: 10 }); assert.equal(result.baseShare, 4_500_000); assert.equal(result.extraExpenses, 800_000); assert.equal(result.totalPayout, 5_300_000); }); it('handles a class with zero sessions held gracefully', () => { const result = calculateProfessorPayout({ payoutType: 'hourly', payoutHourlyRate: 300_000, sessionDurationHours: 1.5, extraExpensePerSession: 80_000, sessionsCount: 0 }); assert.equal(result.baseShare, 0); assert.equal(result.extraExpenses, 0); assert.equal(result.totalPayout, 0); }); }); describe('calculateNetProfit', () => { it('subtracts payout from income', () => { assert.equal(calculateNetProfit({ totalIncome: 10_000_000, professorTotalPayout: 4_500_000 }), 5_500_000); }); it('can go negative to represent a loss', () => { assert.equal(calculateNetProfit({ totalIncome: 1_000_000, professorTotalPayout: 4_500_000 }), -3_500_000); }); }); describe('calculatePendingReceivables / calculateOverpaidAmount', () => { it('computes outstanding receivables', () => { assert.equal(calculatePendingReceivables(10_000_000, 6_000_000), 4_000_000); }); it('never returns a negative outstanding amount when overpaid', () => { assert.equal(calculatePendingReceivables(10_000_000, 12_000_000), 0); assert.equal(calculateOverpaidAmount(10_000_000, 12_000_000), 2_000_000); }); it('returns 0 overpaid when underpaid', () => { assert.equal(calculateOverpaidAmount(10_000_000, 6_000_000), 0); }); it('handles a class with zero expected revenue (no students)', () => { assert.equal(calculatePendingReceivables(0, 0), 0); assert.equal(calculateOverpaidAmount(0, 0), 0); }); }); describe('calculateStudentRevenuePerSession', () => { it('divides tuition evenly across planned sessions', () => { assert.equal(calculateStudentRevenuePerSession(12_000_000, 12), 1_000_000); }); it('returns 0 when there are no planned sessions (avoids division by zero)', () => { assert.equal(calculateStudentRevenuePerSession(12_000_000, 0), 0); }); });