Add professor share & financial reports UI, expense management page
Adds payout configuration fields (percentage/hourly + per-session expense allowance) with a live payout preview to the class form, a new Financial Reports page (per-class and date-range/monthly tabs, per-session drill-down dialog) with reusable stat cards, and an Institutional Expenses management page. Registers routes, sidebar navigation, and permission constants for the new financial_reports and expenses permissions.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// /src/utils/professorShare.js
|
||||
// Mirrors gameno-api/utils/professorShare.js so the dashboard can preview payout
|
||||
// calculations client-side before saving a class.
|
||||
|
||||
const toNonNegativeNumber = (value) => {
|
||||
const n = Number(value);
|
||||
if (!Number.isFinite(n) || n < 0) return 0;
|
||||
return n;
|
||||
};
|
||||
|
||||
const toPercentage = (value) => Math.min(100, toNonNegativeNumber(value));
|
||||
|
||||
const parseClockTime = (value) => {
|
||||
const raw = String(value || '').trim();
|
||||
const match = raw.match(/^(\d{1,2}):(\d{2})$/);
|
||||
if (!match) return null;
|
||||
const hours = Number(match[1]);
|
||||
const minutes = Number(match[2]);
|
||||
if (hours > 23 || minutes > 59) return null;
|
||||
return hours * 60 + minutes;
|
||||
};
|
||||
|
||||
export function calculateSessionDurationHours(startTime, endTime) {
|
||||
const start = parseClockTime(startTime);
|
||||
const end = parseClockTime(endTime);
|
||||
if (start == null || end == null) return 0;
|
||||
let diffMinutes = end - start;
|
||||
if (diffMinutes <= 0) diffMinutes += 24 * 60;
|
||||
return diffMinutes / 60;
|
||||
}
|
||||
|
||||
export function resolveSessionDurationHours({ hoursPerSection, startTime, endTime } = {}) {
|
||||
const hours = toNonNegativeNumber(hoursPerSection);
|
||||
if (hours > 0) return hours;
|
||||
return calculateSessionDurationHours(startTime, endTime);
|
||||
}
|
||||
|
||||
export function calculatePercentageShare({ payoutPercentage = 0, revenue = 0 } = {}) {
|
||||
return (toPercentage(payoutPercentage) / 100) * toNonNegativeNumber(revenue);
|
||||
}
|
||||
|
||||
export function calculateHourlyShare({ payoutHourlyRate = 0, sessionDurationHours = 0, sessionsCount = 0 } = {}) {
|
||||
return toNonNegativeNumber(payoutHourlyRate) * toNonNegativeNumber(sessionDurationHours) * toNonNegativeNumber(sessionsCount);
|
||||
}
|
||||
|
||||
export function calculateExtraExpenses({ extraExpensePerSession = 0, sessionsCount = 0 } = {}) {
|
||||
return toNonNegativeNumber(extraExpensePerSession) * toNonNegativeNumber(sessionsCount);
|
||||
}
|
||||
|
||||
export function calculateProfessorPayout(params = {}) {
|
||||
const baseShare = params.payoutType === 'hourly'
|
||||
? calculateHourlyShare(params)
|
||||
: calculatePercentageShare(params);
|
||||
const extraExpenses = calculateExtraExpenses(params);
|
||||
return {
|
||||
baseShare,
|
||||
extraExpenses,
|
||||
totalPayout: baseShare + extraExpenses
|
||||
};
|
||||
}
|
||||
|
||||
export function calculateNetProfit({ totalIncome = 0, professorTotalPayout = 0 } = {}) {
|
||||
return toNonNegativeNumber(totalIncome) - toNonNegativeNumber(professorTotalPayout);
|
||||
}
|
||||
|
||||
export function calculatePendingReceivables(expectedRevenue = 0, actualReceivedRevenue = 0) {
|
||||
return Math.max(0, toNonNegativeNumber(expectedRevenue) - toNonNegativeNumber(actualReceivedRevenue));
|
||||
}
|
||||
|
||||
export function calculateOverpaidAmount(expectedRevenue = 0, actualReceivedRevenue = 0) {
|
||||
return Math.max(0, toNonNegativeNumber(actualReceivedRevenue) - toNonNegativeNumber(expectedRevenue));
|
||||
}
|
||||
Reference in New Issue
Block a user