// continuity/follow-up.ts
// Follow up on quotes that go quiet. Email only; stop on reply.
import { sendEmail } from './mail';
import { daysSince } from './dates';
const CADENCE = [3, 7, 14]; // days after the quote goes out
export function nextFollowUp(quote: Quote): number | null {
if (quote.repliedAt) return null; // they answered: stop
const sent = quote.followUps.length;
if (sent >= CADENCE.length) return null; // asked enough
const due = CADENCE[sent];
return daysSince(quote.sentAt) >= due ? due : null;
}
export async function runFollowUps(quotes: Quote[]) {
for (const quote of quotes) {
const day = nextFollowUp(quote);
if (day === null) continue;
await sendEmail({
to: quote.customer.email,
subject: `Checking in on your ${quote.project} quote`,
body: draftFollowUp(quote, day),
});
quote.followUps.push({ day, sentAt: new Date() });
}
}
// continuity/pay-application.ts
// Tracks the numbers. Never touches the money.
export function payApplication(job: Job): PayApp {
const contractSum = job.contract + sum(job.changeOrders);
const completed = round(contractSum * job.percentComplete);
const retainage = round(completed * job.retainageRate);
const earned = completed - retainage;
const currentDue = round(earned - job.previouslyBilled);
return {
contractSum,
completed,
retainage,
earned,
currentDue: Math.max(0, currentDue),
};
}
function round(n: number): number {
return Math.round(n * 100) / 100;
}
function sum(values: number[]): number {
return values.reduce((total, v) => total + v, 0);
}