removed unneccesary files
This commit is contained in:
@@ -10,6 +10,11 @@ import { PrismaClient } from "@prisma/client";
|
||||
import { z } from "zod";
|
||||
import { getUserMidnightFromDateOnly } from "./allocator.js";
|
||||
import { fromZonedTime, toZonedTime } from "date-fns-tz";
|
||||
import {
|
||||
computeDepositShares,
|
||||
computeOverdraftShares,
|
||||
computeWithdrawShares,
|
||||
} from "./services/category-shares.js";
|
||||
import healthRoutes from "./routes/health.js";
|
||||
import sessionRoutes from "./routes/session.js";
|
||||
import userRoutes from "./routes/user.js";
|
||||
@@ -429,199 +434,26 @@ function calculateNextDueDate(currentDueDate: Date, frequency: string, timezone:
|
||||
|
||||
switch (frequency) {
|
||||
case "weekly":
|
||||
zoned.setUTCDate(zoned.getUTCDate() + 7);
|
||||
zoned.setDate(zoned.getDate() + 7);
|
||||
break;
|
||||
case "biweekly":
|
||||
zoned.setUTCDate(zoned.getUTCDate() + 14);
|
||||
zoned.setDate(zoned.getDate() + 14);
|
||||
break;
|
||||
case "monthly": {
|
||||
const targetDay = zoned.getUTCDate();
|
||||
const nextMonth = zoned.getUTCMonth() + 1;
|
||||
const nextYear = zoned.getUTCFullYear() + Math.floor(nextMonth / 12);
|
||||
const nextMonthIndex = nextMonth % 12;
|
||||
const lastDay = new Date(Date.UTC(nextYear, nextMonthIndex + 1, 0)).getUTCDate();
|
||||
zoned.setUTCFullYear(nextYear, nextMonthIndex, Math.min(targetDay, lastDay));
|
||||
const targetDay = zoned.getDate();
|
||||
zoned.setDate(1);
|
||||
zoned.setMonth(zoned.getMonth() + 1);
|
||||
const lastDay = new Date(zoned.getFullYear(), zoned.getMonth() + 1, 0).getDate();
|
||||
zoned.setDate(Math.min(targetDay, lastDay));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return base;
|
||||
}
|
||||
|
||||
zoned.setUTCHours(0, 0, 0, 0);
|
||||
zoned.setHours(0, 0, 0, 0);
|
||||
return fromZonedTime(zoned, timezone);
|
||||
}
|
||||
function jsonBigIntSafe(obj: unknown) {
|
||||
return JSON.parse(
|
||||
JSON.stringify(obj, (_k, v) => (typeof v === "bigint" ? Number(v) : v))
|
||||
);
|
||||
}
|
||||
|
||||
type PercentCategory = {
|
||||
id: string;
|
||||
percent: number;
|
||||
balanceCents: bigint | null;
|
||||
};
|
||||
|
||||
function computePercentShares(categories: PercentCategory[], amountCents: number) {
|
||||
const percentTotal = categories.reduce((sum, cat) => sum + cat.percent, 0);
|
||||
if (percentTotal <= 0) return { ok: false as const, reason: "no_percent" };
|
||||
|
||||
const shares = categories.map((cat) => {
|
||||
const raw = (amountCents * cat.percent) / percentTotal;
|
||||
const floored = Math.floor(raw);
|
||||
return {
|
||||
id: cat.id,
|
||||
balanceCents: Number(cat.balanceCents ?? 0n),
|
||||
share: floored,
|
||||
frac: raw - floored,
|
||||
};
|
||||
});
|
||||
|
||||
let remainder = amountCents - shares.reduce((sum, s) => sum + s.share, 0);
|
||||
shares
|
||||
.slice()
|
||||
.sort((a, b) => b.frac - a.frac)
|
||||
.forEach((s) => {
|
||||
if (remainder > 0) {
|
||||
s.share += 1;
|
||||
remainder -= 1;
|
||||
}
|
||||
});
|
||||
|
||||
if (shares.some((s) => s.share > s.balanceCents)) {
|
||||
return { ok: false as const, reason: "insufficient_balances" };
|
||||
}
|
||||
|
||||
return { ok: true as const, shares };
|
||||
}
|
||||
|
||||
function computeWithdrawShares(categories: PercentCategory[], amountCents: number) {
|
||||
const percentTotal = categories.reduce((sum, cat) => sum + cat.percent, 0);
|
||||
if (percentTotal <= 0) return { ok: false as const, reason: "no_percent" };
|
||||
|
||||
const working = categories.map((cat) => ({
|
||||
id: cat.id,
|
||||
percent: cat.percent,
|
||||
balanceCents: Number(cat.balanceCents ?? 0n),
|
||||
share: 0,
|
||||
}));
|
||||
|
||||
let remaining = Math.max(0, Math.floor(amountCents));
|
||||
let safety = 0;
|
||||
|
||||
while (remaining > 0 && safety < 1000) {
|
||||
safety += 1;
|
||||
const eligible = working.filter((c) => c.balanceCents > 0 && c.percent > 0);
|
||||
if (eligible.length === 0) break;
|
||||
|
||||
const totalPercent = eligible.reduce((sum, cat) => sum + cat.percent, 0);
|
||||
if (totalPercent <= 0) break;
|
||||
|
||||
const provisional = eligible.map((cat) => {
|
||||
const raw = (remaining * cat.percent) / totalPercent;
|
||||
const floored = Math.floor(raw);
|
||||
return {
|
||||
id: cat.id,
|
||||
raw,
|
||||
floored,
|
||||
remainder: raw - floored,
|
||||
};
|
||||
});
|
||||
|
||||
let sumBase = provisional.reduce((sum, p) => sum + p.floored, 0);
|
||||
let leftovers = remaining - sumBase;
|
||||
provisional
|
||||
.slice()
|
||||
.sort((a, b) => b.remainder - a.remainder)
|
||||
.forEach((p) => {
|
||||
if (leftovers > 0) {
|
||||
p.floored += 1;
|
||||
leftovers -= 1;
|
||||
}
|
||||
});
|
||||
|
||||
let allocatedThisRound = 0;
|
||||
for (const p of provisional) {
|
||||
const entry = working.find((w) => w.id === p.id);
|
||||
if (!entry) continue;
|
||||
const take = Math.min(p.floored, entry.balanceCents);
|
||||
if (take > 0) {
|
||||
entry.balanceCents -= take;
|
||||
entry.share += take;
|
||||
allocatedThisRound += take;
|
||||
}
|
||||
}
|
||||
|
||||
remaining -= allocatedThisRound;
|
||||
if (allocatedThisRound === 0) break;
|
||||
}
|
||||
|
||||
if (remaining > 0) {
|
||||
return { ok: false as const, reason: "insufficient_balances" };
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true as const,
|
||||
shares: working.map((c) => ({ id: c.id, share: c.share })),
|
||||
};
|
||||
}
|
||||
|
||||
function computeOverdraftShares(categories: PercentCategory[], amountCents: number) {
|
||||
const percentTotal = categories.reduce((sum, cat) => sum + cat.percent, 0);
|
||||
if (percentTotal <= 0) return { ok: false as const, reason: "no_percent" };
|
||||
|
||||
const shares = categories.map((cat) => {
|
||||
const raw = (amountCents * cat.percent) / percentTotal;
|
||||
const floored = Math.floor(raw);
|
||||
return {
|
||||
id: cat.id,
|
||||
share: floored,
|
||||
frac: raw - floored,
|
||||
};
|
||||
});
|
||||
|
||||
let remainder = amountCents - shares.reduce((sum, s) => sum + s.share, 0);
|
||||
shares
|
||||
.slice()
|
||||
.sort((a, b) => b.frac - a.frac)
|
||||
.forEach((s) => {
|
||||
if (remainder > 0) {
|
||||
s.share += 1;
|
||||
remainder -= 1;
|
||||
}
|
||||
});
|
||||
|
||||
return { ok: true as const, shares };
|
||||
}
|
||||
|
||||
function computeDepositShares(categories: PercentCategory[], amountCents: number) {
|
||||
const percentTotal = categories.reduce((sum, cat) => sum + cat.percent, 0);
|
||||
if (percentTotal <= 0) return { ok: false as const, reason: "no_percent" };
|
||||
|
||||
const shares = categories.map((cat) => {
|
||||
const raw = (amountCents * cat.percent) / percentTotal;
|
||||
const floored = Math.floor(raw);
|
||||
return {
|
||||
id: cat.id,
|
||||
share: floored,
|
||||
frac: raw - floored,
|
||||
};
|
||||
});
|
||||
|
||||
let remainder = amountCents - shares.reduce((sum, s) => sum + s.share, 0);
|
||||
shares
|
||||
.slice()
|
||||
.sort((a, b) => b.frac - a.frac)
|
||||
.forEach((s) => {
|
||||
if (remainder > 0) {
|
||||
s.share += 1;
|
||||
remainder -= 1;
|
||||
}
|
||||
});
|
||||
|
||||
return { ok: true as const, shares };
|
||||
}
|
||||
|
||||
const DEFAULT_VARIABLE_CATEGORIES = [
|
||||
{ name: "Essentials", percent: 50, priority: 10, isSavings: false },
|
||||
{ name: "Savings", percent: 30, priority: 20, isSavings: true },
|
||||
|
||||
Reference in New Issue
Block a user