Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
import { addDays } from "./dates_add_days.ts"; ← from dates.add-days ^1.0.0 · built alongside by fune
import { addMonths } from "./dates_add_months.ts"; ← from dates.add-months ^1.0.0 · built alongside by fune
import { daysBetween } from "./dates_days_between.ts"; ← from dates.days-between ^1.0.0 · built alongside by fune
import { roundDiv } from "./math_round_div.ts"; ← from math.round-div ^1.0.0 · built alongside by fune
import { type ServiceDue, type ServiceTrigger } from "./fleet_service_due_types.ts";
function wholeNumber(name: string, value: number): void {
if (!Number.isInteger(value) || value < 0) {
throw new RangeError(`${name} must be a whole number, 0 or more, received ${value}`);
}
}
/**
* The next service, by time or mileage, whichever comes first.
*
* "Whichever comes first" needs a date for the mileage limit too, so it is
* projected from the average daily mileage since the last service, rounded up
* to a whole day: a service booked for the projected day is never late on
* that estimate.
*/
export function serviceDue(
lastServiceDate: string,
lastServiceMileage: number,
intervalMonths: number,
intervalMiles: number,
onDate: string,
currentMileage: number
): ServiceDue {
wholeNumber("lastServiceMileage", lastServiceMileage);
wholeNumber("intervalMonths", intervalMonths);
wholeNumber("intervalMiles", intervalMiles);
wholeNumber("currentMileage", currentMileage);
if (intervalMonths === 0 && intervalMiles === 0) {
throw new RangeError("at least one of intervalMonths and intervalMiles must be above zero");
}
const daysSince = daysBetween(lastServiceDate, onDate);
if (daysSince < 0) {
throw new RangeError(`onDate ${onDate} is before the last service on ${lastServiceDate}`);
}
const milesSince = currentMileage - lastServiceMileage;
if (milesSince < 0) {
throw new RangeError(`currentMileage ${currentMileage} is below the mileage at the last service, ${lastServiceMileage}`);
}
const dueDate = intervalMonths > 0 ? addMonths(lastServiceDate, intervalMonths) : null;
const daysRemaining = dueDate === null ? null : daysBetween(onDate, dueDate);
const dueMileage = intervalMiles > 0 ? lastServiceMileage + intervalMiles : null;
const milesRemaining = dueMileage === null ? null : dueMileage - currentMileage;
let projectedMileageDate: string | null = null;
let mileageDate: string | null = null;
if (milesRemaining !== null) {
if (milesRemaining <= 0) {
mileageDate = onDate;
} else if (daysSince > 0 && milesSince > 0) {
projectedMileageDate = addDays(onDate, roundDiv(milesRemaining * daysSince, milesSince, "up"));
mileageDate = projectedMileageDate;
}
}
let nextDueDate: string | null = dueDate;
let dueBy: ServiceTrigger | null = dueDate === null ? null : "date";
if (mileageDate !== null && (nextDueDate === null || mileageDate < nextDueDate)) {
nextDueDate = mileageDate;
dueBy = "mileage";
}
return {
dueDate,
dueMileage,
daysRemaining,
milesRemaining,
projectedMileageDate,
nextDueDate,
dueBy,
overdue: (daysRemaining !== null && daysRemaining < 0) || (milesRemaining !== null && milesRemaining < 0),
};
}