81 lines
2.1 KiB
TypeScript
81 lines
2.1 KiB
TypeScript
export type CheckRassInput = {
|
|
amount: number;
|
|
dueDate: Date | string;
|
|
};
|
|
|
|
export type CheckRassResult = {
|
|
checkCount: number;
|
|
totalAmount: number;
|
|
nearestDueDate: string | null;
|
|
farthestDueDate: string | null;
|
|
rassDays: number;
|
|
rassDate: string | null;
|
|
};
|
|
|
|
import {
|
|
addDays,
|
|
calendarDayOf,
|
|
diffInDays,
|
|
tehranCalendarDay,
|
|
} from './tehran-time';
|
|
|
|
/** وضعیتی که فاکتور قیمتگذاریشده برای مشتری ارسال میشود. */
|
|
const INVOICE_ISSUED_STATUS = 'awaiting_settlement';
|
|
|
|
/**
|
|
* مبدأ راس چک، تاریخ صدور فاکتور است نه امروز و نه تاریخ ثبت پیشسفارش.
|
|
* اگر فاکتور دوباره ارسال شده باشد، آخرین صدور ملاک است.
|
|
*/
|
|
export function invoiceAnchorDate(
|
|
createdAt: Date,
|
|
history: { toStatus: string; changedAt: Date }[] = [],
|
|
): Date {
|
|
for (let i = history.length - 1; i >= 0; i -= 1) {
|
|
const row = history[i];
|
|
if (row?.toStatus === INVOICE_ISSUED_STATUS) return row.changedAt;
|
|
}
|
|
return createdAt;
|
|
}
|
|
|
|
export function calculateCheckRass(
|
|
checks: CheckRassInput[],
|
|
baseDate: Date,
|
|
): CheckRassResult {
|
|
if (checks.length === 0) {
|
|
return {
|
|
checkCount: 0,
|
|
totalAmount: 0,
|
|
nearestDueDate: null,
|
|
farthestDueDate: null,
|
|
rassDays: 0,
|
|
rassDate: null,
|
|
};
|
|
}
|
|
|
|
const base = tehranCalendarDay(baseDate);
|
|
let totalAmount = 0;
|
|
let weightedDays = 0;
|
|
const dueDates: Date[] = [];
|
|
|
|
for (const check of checks) {
|
|
const due = calendarDayOf(check.dueDate);
|
|
dueDates.push(due);
|
|
const days = Math.max(0, diffInDays(base, due));
|
|
totalAmount += check.amount;
|
|
weightedDays += check.amount * days;
|
|
}
|
|
|
|
const rassDays = totalAmount > 0 ? Math.round(weightedDays / totalAmount) : 0;
|
|
const rassDate = addDays(base, rassDays);
|
|
dueDates.sort((a, b) => a.getTime() - b.getTime());
|
|
|
|
return {
|
|
checkCount: checks.length,
|
|
totalAmount,
|
|
nearestDueDate: dueDates[0]?.toISOString() ?? null,
|
|
farthestDueDate: dueDates[dueDates.length - 1]?.toISOString() ?? null,
|
|
rassDays,
|
|
rassDate: rassDate.toISOString(),
|
|
};
|
|
}
|