fiddy/apps/web/lib/server/recurring-entries.ts
Nico f8e426542d
Some checks failed
Build & Deploy Fiddy (Dokploy) / build (push) Has been cancelled
Build & Deploy Fiddy (Dokploy) / deploy (push) Has been cancelled
feat: implement schedules pivot, scheduler service, and dokploy deploy flow
2026-02-15 17:10:58 -08:00

135 lines
4.3 KiB
TypeScript

if (process.env.NODE_ENV !== "test")
require("server-only");
import { createSchedule, deleteSchedule, listSchedules, requireActiveGroup, updateSchedule } from "@/lib/server/schedules";
import type { Schedule, ScheduleEndCondition, ScheduleFrequency } from "@/lib/shared/types";
export { requireActiveGroup };
type RecurringEntryCompat = {
id: number;
entryType: "SPENDING" | "INCOME";
amountDollars: number;
occurredAt: string;
necessity: "NECESSARY" | "BOTH" | "UNNECESSARY";
purchaseType: string;
notes: string | null;
receiptId: number | null;
bucketId: number | null;
tags: string[];
isRecurring: true;
frequency: ScheduleFrequency;
intervalCount: number;
endCondition: ScheduleEndCondition;
endCount: number | null;
endDate: string | null;
nextRunAt: string | null;
lastExecutedAt: string | null;
};
function toRecurringCompat(schedule: Schedule): RecurringEntryCompat {
return {
id: schedule.id,
entryType: schedule.entryType,
amountDollars: schedule.amountDollars,
occurredAt: schedule.startsOn,
necessity: schedule.necessity,
purchaseType: schedule.purchaseType,
notes: schedule.notes,
receiptId: null,
bucketId: null,
tags: schedule.tags,
isRecurring: true,
frequency: schedule.frequency,
intervalCount: schedule.intervalCount,
endCondition: schedule.endCondition,
endCount: schedule.endCount,
endDate: schedule.endDate,
nextRunAt: schedule.nextRunOn,
lastExecutedAt: schedule.lastRunOn
};
}
export async function listRecurringEntries(groupId: number): Promise<RecurringEntryCompat[]> {
const schedules = await listSchedules(groupId);
return schedules.map(toRecurringCompat);
}
export async function createRecurringEntry(input: {
groupId: number;
userId: number;
entryType: "SPENDING" | "INCOME";
amountDollars: number;
occurredAt: string;
necessity: "NECESSARY" | "BOTH" | "UNNECESSARY";
purchaseType: string;
notes?: string;
tags?: string[];
frequency?: ScheduleFrequency | null;
intervalCount?: number;
endCondition?: ScheduleEndCondition | null;
endCount?: number | null;
endDate?: string | null;
}) {
const schedule = await createSchedule({
groupId: input.groupId,
userId: input.userId,
entryType: input.entryType,
amountDollars: input.amountDollars,
necessity: input.necessity,
purchaseType: input.purchaseType,
notes: input.notes,
tags: input.tags,
startsOn: input.occurredAt,
frequency: input.frequency || "MONTHLY",
intervalCount: input.intervalCount || 1,
endCondition: input.endCondition || "NEVER",
endCount: input.endCount ?? null,
endDate: input.endDate || null,
createEntryNow: true
});
return toRecurringCompat(schedule);
}
export async function updateRecurringEntry(input: {
id: number;
groupId: number;
userId: number;
entryType: "SPENDING" | "INCOME";
amountDollars: number;
occurredAt: string;
necessity: "NECESSARY" | "BOTH" | "UNNECESSARY";
purchaseType: string;
notes?: string;
tags?: string[];
frequency?: ScheduleFrequency | null;
intervalCount?: number;
endCondition?: ScheduleEndCondition | null;
endCount?: number | null;
endDate?: string | null;
nextRunAt?: string | null;
}) {
const schedule = await updateSchedule({
id: input.id,
groupId: input.groupId,
userId: input.userId,
entryType: input.entryType,
amountDollars: input.amountDollars,
necessity: input.necessity,
purchaseType: input.purchaseType,
notes: input.notes,
tags: input.tags,
startsOn: input.occurredAt,
frequency: input.frequency || "MONTHLY",
intervalCount: input.intervalCount || 1,
endCondition: input.endCondition || "NEVER",
endCount: input.endCount ?? null,
endDate: input.endDate || null,
nextRunOn: input.nextRunAt || input.occurredAt
});
return schedule ? toRecurringCompat(schedule) : null;
}
export async function deleteRecurringEntry(input: { id: number; groupId: number; userId: number }) {
return deleteSchedule(input);
}