final touches for beta skymoney (at least i think)

This commit is contained in:
2026-01-18 00:00:44 -06:00
parent 4eae966f96
commit f4f0ae5df2
161 changed files with 26016 additions and 1966 deletions

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "passwordHash" TEXT,
ADD COLUMN "updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;

View File

@@ -0,0 +1,9 @@
-- AddForeignKey
ALTER TABLE "Transaction"
ADD CONSTRAINT "Transaction_categoryId_fkey"
FOREIGN KEY ("categoryId") REFERENCES "VariableCategory"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Transaction"
ADD CONSTRAINT "Transaction_planId_fkey"
FOREIGN KEY ("planId") REFERENCES "FixedPlan"("id") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "User" ALTER COLUMN "updatedAt" DROP DEFAULT;
-- AlterTable
ALTER TABLE "VariableCategory" ADD COLUMN "savingsTargetCents" BIGINT;

View File

@@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "Transaction" ADD COLUMN "isReconciled" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "note" TEXT,
ADD COLUMN "receiptUrl" TEXT;

View File

@@ -0,0 +1,4 @@
-- AlterTable
ALTER TABLE "FixedPlan" ADD COLUMN "autoRollover" BOOLEAN NOT NULL DEFAULT true,
ADD COLUMN "lastRollover" TIMESTAMP(3),
ADD COLUMN "periodDays" INTEGER NOT NULL DEFAULT 30;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "displayName" TEXT;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "IncomeEvent" ADD COLUMN "note" TEXT;

View File

@@ -0,0 +1,6 @@
-- AlterTable
ALTER TABLE "FixedPlan" ADD COLUMN "autoPayEnabled" BOOLEAN NOT NULL DEFAULT false,
ADD COLUMN "lastAutoPayment" TIMESTAMP(3),
ADD COLUMN "maxRetryAttempts" INTEGER NOT NULL DEFAULT 3,
ADD COLUMN "nextPaymentDate" TIMESTAMP(3),
ADD COLUMN "paymentSchedule" JSONB;

View File

@@ -0,0 +1,14 @@
-- CreateEnum
CREATE TYPE "FundingStrategy" AS ENUM ('tight', 'moderate', 'comfortable');
-- CreateEnum
CREATE TYPE "IncomeFrequency" AS ENUM ('weekly', 'biweekly', 'monthly', 'irregular');
-- AlterTable
ALTER TABLE "FixedPlan" ADD COLUMN "currentFundedCents" BIGINT NOT NULL DEFAULT 0,
ADD COLUMN "lastFundingDate" TIMESTAMP(3);
-- AlterTable
ALTER TABLE "User" ADD COLUMN "fundingStrategy" "FundingStrategy" NOT NULL DEFAULT 'moderate',
ADD COLUMN "incomeFrequency" "IncomeFrequency" NOT NULL DEFAULT 'irregular',
ADD COLUMN "typicalIncomeCents" BIGINT;

View File

@@ -0,0 +1,26 @@
/*
Warnings:
- The values [irregular] on the enum `IncomeFrequency` will be removed. If these variants are still used in the database, this will fail.
- You are about to drop the column `fundingStrategy` on the `User` table. All the data in the column will be lost.
- You are about to drop the column `typicalIncomeCents` on the `User` table. All the data in the column will be lost.
*/
-- AlterEnum
BEGIN;
CREATE TYPE "IncomeFrequency_new" AS ENUM ('weekly', 'biweekly', 'monthly');
ALTER TABLE "User" ALTER COLUMN "incomeFrequency" DROP DEFAULT;
ALTER TABLE "User" ALTER COLUMN "incomeFrequency" TYPE "IncomeFrequency_new" USING ("incomeFrequency"::text::"IncomeFrequency_new");
ALTER TYPE "IncomeFrequency" RENAME TO "IncomeFrequency_old";
ALTER TYPE "IncomeFrequency_new" RENAME TO "IncomeFrequency";
DROP TYPE "IncomeFrequency_old";
ALTER TABLE "User" ALTER COLUMN "incomeFrequency" SET DEFAULT 'biweekly';
COMMIT;
-- AlterTable
ALTER TABLE "User" DROP COLUMN "fundingStrategy",
DROP COLUMN "typicalIncomeCents",
ALTER COLUMN "incomeFrequency" SET DEFAULT 'biweekly';
-- DropEnum
DROP TYPE "FundingStrategy";

View File

@@ -0,0 +1,32 @@
-- CreateEnum
CREATE TYPE "IncomeType" AS ENUM ('regular', 'irregular');
-- AlterTable
ALTER TABLE "User" ADD COLUMN "budgetPeriod" TEXT NOT NULL DEFAULT 'monthly',
ADD COLUMN "incomeType" "IncomeType" NOT NULL DEFAULT 'regular',
ADD COLUMN "totalBudgetCents" BIGINT;
-- CreateTable
CREATE TABLE "BudgetSession" (
"id" TEXT NOT NULL,
"userId" TEXT NOT NULL,
"periodStart" TIMESTAMP(3) NOT NULL,
"periodEnd" TIMESTAMP(3) NOT NULL,
"totalBudgetCents" BIGINT NOT NULL,
"allocatedCents" BIGINT NOT NULL DEFAULT 0,
"fundedCents" BIGINT NOT NULL DEFAULT 0,
"availableCents" BIGINT NOT NULL DEFAULT 0,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "BudgetSession_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE INDEX "BudgetSession_userId_periodStart_idx" ON "BudgetSession"("userId", "periodStart");
-- CreateIndex
CREATE UNIQUE INDEX "BudgetSession_userId_periodStart_key" ON "BudgetSession"("userId", "periodStart");
-- AddForeignKey
ALTER TABLE "BudgetSession" ADD CONSTRAINT "BudgetSession_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "FixedPlan" ADD COLUMN "lastFundedPayPeriod" TIMESTAMP(3),
ADD COLUMN "needsFundingThisPeriod" BOOLEAN NOT NULL DEFAULT true;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "firstIncomeDate" TIMESTAMP(3);

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "VariableCategory" ADD COLUMN "isLocked" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -0,0 +1 @@
-- This is an empty migration.

View File

@@ -0,0 +1,8 @@
/*
Warnings:
- You are about to drop the column `isLocked` on the `VariableCategory` table. All the data in the column will be lost.
*/
-- AlterTable
ALTER TABLE "VariableCategory" DROP COLUMN "isLocked";

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "FixedPlan" ADD COLUMN "frequency" TEXT;

View File

@@ -0,0 +1,5 @@
-- AlterTable
ALTER TABLE "IncomeEvent" ADD COLUMN "isScheduledIncome" BOOLEAN NOT NULL DEFAULT false;
-- AlterTable
ALTER TABLE "User" ADD COLUMN "pendingScheduledIncome" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "timezone" TEXT NOT NULL DEFAULT 'America/New_York';

View File

@@ -0,0 +1,4 @@
-- Add overdue tracking fields to FixedPlan
ALTER TABLE "FixedPlan" ADD COLUMN "isOverdue" BOOLEAN NOT NULL DEFAULT false;
ALTER TABLE "FixedPlan" ADD COLUMN "overdueAmount" BIGINT NOT NULL DEFAULT 0;
ALTER TABLE "FixedPlan" ADD COLUMN "overdueSince" TIMESTAMP(3);

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Transaction" ADD COLUMN "isAutoPayment" BOOLEAN NOT NULL DEFAULT false;

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "fixedExpensePercentage" INTEGER NOT NULL DEFAULT 40;

View File

@@ -9,16 +9,40 @@ datasource db {
url = env("DATABASE_URL")
}
enum IncomeFrequency {
weekly
biweekly
monthly
}
enum IncomeType {
regular
irregular
}
model User {
id String @id @default(uuid())
email String @unique
createdAt DateTime @default(now())
id String @id @default(uuid())
email String @unique
passwordHash String?
displayName String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
incomeFrequency IncomeFrequency @default(biweekly)
incomeType IncomeType @default(regular)
firstIncomeDate DateTime? // Track when user's first income was received for accurate pay period calculation
pendingScheduledIncome Boolean @default(false) // Flag set when user dismisses payday overlay, cleared when paycheck is entered
timezone String @default("America/New_York") // IANA timezone identifier for accurate date calculations
fixedExpensePercentage Int @default(40) // Irregular income: percent of new income to auto-fund fixed expenses
totalBudgetCents BigInt?
budgetPeriod String @default("monthly")
variableCategories VariableCategory[]
fixedPlans FixedPlan[]
incomes IncomeEvent[]
allocations Allocation[]
transactions Transaction[]
budgetSessions BudgetSession[]
}
model VariableCategory {
@@ -30,6 +54,8 @@ model VariableCategory {
priority Int @default(100)
isSavings Boolean @default(false)
balanceCents BigInt @default(0)
savingsTargetCents BigInt?
transactions Transaction[] @relation("TransactionCategory")
@@unique([userId, name])
@@index([userId, priority])
@@ -44,9 +70,31 @@ model FixedPlan {
dueOn DateTime
totalCents BigInt
fundedCents BigInt @default(0)
currentFundedCents BigInt @default(0)
priority Int @default(100)
fundingMode String @default("auto-on-deposit")
scheduleJson Json?
periodDays Int @default(30)
frequency String? // "one-time", "weekly", "biweekly", "monthly"
autoRollover Boolean @default(true)
lastRollover DateTime?
lastFundingDate DateTime?
lastFundedPayPeriod DateTime? // Track when plan was last funded in a pay period
needsFundingThisPeriod Boolean @default(true) // Simple flag to track funding needs
// Auto-payment fields
autoPayEnabled Boolean @default(false)
paymentSchedule Json? // { frequency: "monthly", dayOfMonth: 1, minFundingPercent: 100 }
nextPaymentDate DateTime?
lastAutoPayment DateTime?
maxRetryAttempts Int @default(3)
// Overdue tracking fields
isOverdue Boolean @default(false)
overdueAmount BigInt @default(0)
overdueSince DateTime?
transactions Transaction[] @relation("TransactionPlan")
@@unique([userId, name])
@@index([userId, dueOn])
@@ -59,6 +107,8 @@ model IncomeEvent {
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
postedAt DateTime
amountCents BigInt
note String?
isScheduledIncome Boolean @default(false) // True if this is a regular paycheck (vs bonus/extra income)
allocations Allocation[]
@@index([userId, postedAt])
@@ -83,8 +133,31 @@ model Transaction {
occurredAt DateTime
kind String
categoryId String?
category VariableCategory? @relation("TransactionCategory", fields: [categoryId], references: [id], onDelete: SetNull)
planId String?
plan FixedPlan? @relation("TransactionPlan", fields: [planId], references: [id], onDelete: SetNull)
amountCents BigInt
note String?
receiptUrl String?
isAutoPayment Boolean @default(false)
isReconciled Boolean @default(false)
@@index([userId, occurredAt])
}
model BudgetSession {
id String @id @default(uuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
periodStart DateTime
periodEnd DateTime
totalBudgetCents BigInt
allocatedCents BigInt @default(0)
fundedCents BigInt @default(0)
availableCents BigInt @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([userId, periodStart])
@@index([userId, periodStart])
}

View File

@@ -11,22 +11,39 @@ async function main() {
// 1) User
await prisma.user.upsert({
where: { id: userId },
create: { id: userId, email: "demo@example.com" },
update: {},
create: {
id: userId,
email: "demo@example.com",
incomeFrequency: "biweekly"
},
update: { incomeFrequency: "biweekly" },
});
// 2) Variable categories (sum = 100)
const categories = [
{ name: "Savings", percent: 40, isSavings: true, priority: 10 },
{ name: "Needs", percent: 40, isSavings: false, priority: 20 },
{ name: "Wants", percent: 20, isSavings: false, priority: 30 },
{ name: "Savings", percent: 40, isSavings: true, priority: 10, target: cents(5000) },
{ name: "Needs", percent: 40, isSavings: false, priority: 20 },
{ name: "Wants", percent: 20, isSavings: false, priority: 30 },
];
for (const c of categories) {
await prisma.variableCategory.upsert({
where: { userId_name: { userId, name: c.name } },
create: { userId, name: c.name, percent: c.percent, isSavings: c.isSavings, priority: c.priority, balanceCents: 0n },
update: { percent: c.percent, isSavings: c.isSavings, priority: c.priority },
create: {
userId,
name: c.name,
percent: c.percent,
isSavings: c.isSavings,
priority: c.priority,
balanceCents: 0n,
savingsTargetCents: c.target ?? null,
},
update: {
percent: c.percent,
isSavings: c.isSavings,
priority: c.priority,
savingsTargetCents: c.target ?? null,
},
});
}
@@ -82,4 +99,4 @@ main().catch((e) => {
process.exit(1);
}).finally(async () => {
await prisma.$disconnect();
});
});