408 lines
21 KiB
JavaScript
408 lines
21 KiB
JavaScript
/**
|
|
* LedgerOne Demo Account Seed Script
|
|
* Creates a fully-populated demo account for testing all features.
|
|
*
|
|
* Usage: node seed-demo.mjs
|
|
* Creds: demo@ledgerone.app / Demo1234!
|
|
*/
|
|
|
|
import { PrismaClient } from "@prisma/client";
|
|
import * as crypto from "crypto";
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
const DEMO_EMAIL = "demo@ledgerone.app";
|
|
const DEMO_PASSWORD = "Demo1234!";
|
|
|
|
function hashPassword(password) {
|
|
const salt = crypto.randomBytes(16).toString("hex");
|
|
const hash = crypto.pbkdf2Sync(password, salt, 100_000, 64, "sha512").toString("hex");
|
|
return `${salt}:${hash}`;
|
|
}
|
|
|
|
function daysAgo(n) {
|
|
const d = new Date();
|
|
d.setHours(12, 0, 0, 0);
|
|
d.setDate(d.getDate() - n);
|
|
return d;
|
|
}
|
|
|
|
async function main() {
|
|
console.log("🧹 Cleaning up existing demo account...");
|
|
|
|
const existing = await prisma.user.findUnique({ where: { email: DEMO_EMAIL } });
|
|
if (existing) {
|
|
const uid = existing.id;
|
|
// Delete leaf models first, then parents
|
|
await prisma.auditLog.deleteMany({ where: { userId: uid } });
|
|
await prisma.refreshToken.deleteMany({ where: { userId: uid } });
|
|
await prisma.emailVerificationToken.deleteMany({ where: { userId: uid } });
|
|
await prisma.passwordResetToken.deleteMany({ where: { userId: uid } });
|
|
await prisma.subscription.deleteMany({ where: { userId: uid } });
|
|
await prisma.exportLog.deleteMany({ where: { userId: uid } });
|
|
await prisma.googleConnection.deleteMany({ where: { userId: uid } });
|
|
|
|
const taxReturns = await prisma.taxReturn.findMany({ where: { userId: uid } });
|
|
for (const tr of taxReturns) {
|
|
await prisma.taxDocument.deleteMany({ where: { taxReturnId: tr.id } });
|
|
}
|
|
await prisma.taxReturn.deleteMany({ where: { userId: uid } });
|
|
|
|
const accounts = await prisma.account.findMany({ where: { userId: uid } });
|
|
for (const account of accounts) {
|
|
const txRaws = await prisma.transactionRaw.findMany({ where: { accountId: account.id } });
|
|
for (const tx of txRaws) {
|
|
await prisma.ruleExecution.deleteMany({ where: { transactionId: tx.id } });
|
|
await prisma.transactionDerived.deleteMany({ where: { rawTransactionId: tx.id } });
|
|
}
|
|
await prisma.transactionRaw.deleteMany({ where: { accountId: account.id } });
|
|
}
|
|
await prisma.account.deleteMany({ where: { userId: uid } });
|
|
|
|
const rules = await prisma.rule.findMany({ where: { userId: uid } });
|
|
for (const rule of rules) {
|
|
await prisma.ruleExecution.deleteMany({ where: { ruleId: rule.id } });
|
|
}
|
|
await prisma.rule.deleteMany({ where: { userId: uid } });
|
|
|
|
await prisma.user.delete({ where: { id: uid } });
|
|
console.log(" ✓ Removed previous demo account");
|
|
}
|
|
|
|
// ── User ──────────────────────────────────────────────────────────────────
|
|
console.log("\n👤 Creating demo user...");
|
|
const user = await prisma.user.create({
|
|
data: {
|
|
email: DEMO_EMAIL,
|
|
passwordHash: hashPassword(DEMO_PASSWORD),
|
|
fullName: "Alex Chen",
|
|
emailVerified: true, // skip email verification step
|
|
companyName: "LedgerOne Demo Corp",
|
|
city: "San Francisco",
|
|
state: "CA",
|
|
country: "US",
|
|
},
|
|
});
|
|
console.log(` ✓ ${user.email} (id: ${user.id})`);
|
|
|
|
// ── Subscription ──────────────────────────────────────────────────────────
|
|
await prisma.subscription.create({
|
|
data: {
|
|
userId: user.id,
|
|
plan: "pro",
|
|
currentPeriodEnd: new Date(Date.now() + 30 * 86_400_000),
|
|
cancelAtPeriodEnd: false,
|
|
},
|
|
});
|
|
console.log(" ✓ Pro subscription");
|
|
|
|
// ── Accounts ──────────────────────────────────────────────────────────────
|
|
console.log("\n🏦 Creating accounts...");
|
|
const checking = await prisma.account.create({
|
|
data: {
|
|
userId: user.id,
|
|
institutionName: "Chase Bank",
|
|
accountType: "checking",
|
|
mask: "4521",
|
|
currentBalance: 12847.53,
|
|
availableBalance: 12347.53,
|
|
isoCurrencyCode: "USD",
|
|
isActive: true,
|
|
},
|
|
});
|
|
const credit = await prisma.account.create({
|
|
data: {
|
|
userId: user.id,
|
|
institutionName: "American Express",
|
|
accountType: "credit",
|
|
mask: "2834",
|
|
currentBalance: -2341.88, // negative = you owe this
|
|
availableBalance: 7658.12,
|
|
isoCurrencyCode: "USD",
|
|
isActive: true,
|
|
},
|
|
});
|
|
const savings = await prisma.account.create({
|
|
data: {
|
|
userId: user.id,
|
|
institutionName: "Ally Bank",
|
|
accountType: "savings",
|
|
mask: "9012",
|
|
currentBalance: 28500.00,
|
|
availableBalance: 28500.00,
|
|
isoCurrencyCode: "USD",
|
|
isActive: true,
|
|
},
|
|
});
|
|
console.log(" ✓ Chase Checking *4521");
|
|
console.log(" ✓ Amex Credit *2834");
|
|
console.log(" ✓ Ally Savings *9012");
|
|
|
|
// ── Transactions ──────────────────────────────────────────────────────────
|
|
// Convention: positive = money leaving (expense), negative = money entering (income/deposit)
|
|
const txDataset = [
|
|
// ── INCOME (checking) ──
|
|
{ acct: checking, d: 2, amt: -5800.00, desc: "PAYROLL DIRECT DEPOSIT - ACME CORP", cat: "Income" },
|
|
{ acct: checking, d: 32, amt: -5800.00, desc: "PAYROLL DIRECT DEPOSIT - ACME CORP", cat: "Income" },
|
|
{ acct: checking, d: 62, amt: -5800.00, desc: "PAYROLL DIRECT DEPOSIT - ACME CORP", cat: "Income" },
|
|
{ acct: checking, d: 92, amt: -5800.00, desc: "PAYROLL DIRECT DEPOSIT - ACME CORP", cat: "Income" },
|
|
{ acct: checking, d: 122, amt: -5800.00, desc: "PAYROLL DIRECT DEPOSIT - ACME CORP", cat: "Income" },
|
|
{ acct: checking, d: 152, amt: -5800.00, desc: "PAYROLL DIRECT DEPOSIT - ACME CORP", cat: "Income" },
|
|
{ acct: savings, d: 5, amt: -200.00, desc: "INTEREST EARNED - ALLY BANK", cat: "Income" },
|
|
{ acct: savings, d: 35, amt: -194.50, desc: "INTEREST EARNED - ALLY BANK", cat: "Income" },
|
|
{ acct: savings, d: 65, amt: -201.20, desc: "INTEREST EARNED - ALLY BANK", cat: "Income" },
|
|
|
|
// ── RENT (checking) ──
|
|
{ acct: checking, d: 5, amt: 2200.00, desc: "ACH - RENT - HARBOR VIEW APARTMENTS", cat: "Rent & Mortgage" },
|
|
{ acct: checking, d: 35, amt: 2200.00, desc: "ACH - RENT - HARBOR VIEW APARTMENTS", cat: "Rent & Mortgage" },
|
|
{ acct: checking, d: 65, amt: 2200.00, desc: "ACH - RENT - HARBOR VIEW APARTMENTS", cat: "Rent & Mortgage" },
|
|
{ acct: checking, d: 95, amt: 2200.00, desc: "ACH - RENT - HARBOR VIEW APARTMENTS", cat: "Rent & Mortgage" },
|
|
{ acct: checking, d: 125, amt: 2200.00, desc: "ACH - RENT - HARBOR VIEW APARTMENTS", cat: "Rent & Mortgage" },
|
|
{ acct: checking, d: 155, amt: 2200.00, desc: "ACH - RENT - HARBOR VIEW APARTMENTS", cat: "Rent & Mortgage" },
|
|
|
|
// ── UTILITIES (checking) ──
|
|
{ acct: checking, d: 8, amt: 94.50, desc: "PG&E ELECTRIC BILL PAYMENT", cat: "Utilities" },
|
|
{ acct: checking, d: 38, amt: 87.20, desc: "PG&E ELECTRIC BILL PAYMENT", cat: "Utilities" },
|
|
{ acct: checking, d: 68, amt: 112.80, desc: "PG&E ELECTRIC BILL PAYMENT", cat: "Utilities" },
|
|
{ acct: checking, d: 98, amt: 103.40, desc: "PG&E ELECTRIC BILL PAYMENT", cat: "Utilities" },
|
|
{ acct: checking, d: 10, amt: 65.00, desc: "COMCAST XFINITY INTERNET", cat: "Utilities" },
|
|
{ acct: checking, d: 40, amt: 65.00, desc: "COMCAST XFINITY INTERNET", cat: "Utilities" },
|
|
{ acct: checking, d: 70, amt: 65.00, desc: "COMCAST XFINITY INTERNET", cat: "Utilities" },
|
|
{ acct: checking, d: 100, amt: 65.00, desc: "COMCAST XFINITY INTERNET", cat: "Utilities" },
|
|
|
|
// ── GROCERIES (checking) ──
|
|
{ acct: checking, d: 3, amt: 127.43, desc: "WHOLE FOODS MARKET #1234 SAN FRANCISCO", cat: "Groceries" },
|
|
{ acct: checking, d: 11, amt: 89.22, desc: "TRADER JOE S #456 SF", cat: "Groceries" },
|
|
{ acct: checking, d: 18, amt: 145.67, desc: "WHOLE FOODS MARKET #1234 SAN FRANCISCO", cat: "Groceries" },
|
|
{ acct: checking, d: 25, amt: 73.11, desc: "SAFEWAY #789", cat: "Groceries" },
|
|
{ acct: checking, d: 33, amt: 118.54, desc: "WHOLE FOODS MARKET #1234 SAN FRANCISCO", cat: "Groceries" },
|
|
{ acct: checking, d: 45, amt: 92.30, desc: "TRADER JOE S #456 SF", cat: "Groceries" },
|
|
{ acct: checking, d: 55, amt: 131.20, desc: "WHOLE FOODS MARKET #1234 SAN FRANCISCO", cat: "Groceries" },
|
|
{ acct: checking, d: 70, amt: 85.75, desc: "SAFEWAY #789", cat: "Groceries" },
|
|
{ acct: checking, d: 82, amt: 104.88, desc: "WHOLE FOODS MARKET #1234 SAN FRANCISCO", cat: "Groceries" },
|
|
{ acct: checking, d: 105, amt: 76.42, desc: "TRADER JOE S #456 SF", cat: "Groceries" },
|
|
|
|
// ── HEALTHCARE (checking) ──
|
|
{ acct: checking, d: 15, amt: 30.00, desc: "CVS PHARMACY #1122", cat: "Healthcare" },
|
|
{ acct: checking, d: 60, amt: 250.00, desc: "KAISER PERMANENTE COPAY", cat: "Healthcare" },
|
|
{ acct: checking, d: 110, amt: 45.00, desc: "CVS PHARMACY #1122", cat: "Healthcare" },
|
|
{ acct: checking, d: 140, amt: 180.00, desc: "UCSF MEDICAL CENTER", cat: "Healthcare" },
|
|
|
|
// ── DINING (credit) ──
|
|
{ acct: credit, d: 1, amt: 68.40, desc: "NOBU RESTAURANT SF", cat: "Dining & Restaurants" },
|
|
{ acct: credit, d: 4, amt: 14.50, desc: "STARBUCKS #3421 SAN FRANCISCO CA", cat: "Dining & Restaurants" },
|
|
{ acct: credit, d: 7, amt: 42.80, desc: "CHIPOTLE MEXICAN GRILL", cat: "Dining & Restaurants" },
|
|
{ acct: credit, d: 13, amt: 89.20, desc: "MOURAD RESTAURANT SF", cat: "Dining & Restaurants" },
|
|
{ acct: credit, d: 16, amt: 23.60, desc: "SWEETGREEN #55 SF", cat: "Dining & Restaurants" },
|
|
{ acct: credit, d: 22, amt: 112.50, desc: "BENU RESTAURANT SAN FRANCISCO", cat: "Dining & Restaurants" },
|
|
{ acct: credit, d: 29, amt: 18.90, desc: "STARBUCKS #3421 SAN FRANCISCO CA", cat: "Dining & Restaurants" },
|
|
{ acct: credit, d: 36, amt: 55.30, desc: "IN-N-OUT BURGER #77", cat: "Dining & Restaurants" },
|
|
{ acct: credit, d: 48, amt: 78.10, desc: "NOBU RESTAURANT SF", cat: "Dining & Restaurants" },
|
|
{ acct: credit, d: 75, amt: 32.40, desc: "CHIPOTLE MEXICAN GRILL", cat: "Dining & Restaurants" },
|
|
{ acct: credit, d: 90, amt: 44.20, desc: "THE FRENCH LAUNDRY YOUNTVILLE", cat: "Dining & Restaurants" },
|
|
|
|
// ── SUBSCRIPTIONS (credit) ──
|
|
{ acct: credit, d: 9, amt: 15.99, desc: "NETFLIX.COM SUBSCRIPTION", cat: "Subscriptions" },
|
|
{ acct: credit, d: 9, amt: 9.99, desc: "SPOTIFY PREMIUM", cat: "Subscriptions" },
|
|
{ acct: credit, d: 9, amt: 14.99, desc: "OPENAI CHATGPT PLUS", cat: "Subscriptions" },
|
|
{ acct: credit, d: 9, amt: 19.99, desc: "GITHUB COPILOT SUBSCRIPTION", cat: "Subscriptions" },
|
|
{ acct: credit, d: 39, amt: 15.99, desc: "NETFLIX.COM SUBSCRIPTION", cat: "Subscriptions" },
|
|
{ acct: credit, d: 39, amt: 9.99, desc: "SPOTIFY PREMIUM", cat: "Subscriptions" },
|
|
{ acct: credit, d: 39, amt: 14.99, desc: "OPENAI CHATGPT PLUS", cat: "Subscriptions" },
|
|
{ acct: credit, d: 39, amt: 19.99, desc: "GITHUB COPILOT SUBSCRIPTION", cat: "Subscriptions" },
|
|
{ acct: credit, d: 69, amt: 15.99, desc: "NETFLIX.COM SUBSCRIPTION", cat: "Subscriptions" },
|
|
{ acct: credit, d: 69, amt: 9.99, desc: "SPOTIFY PREMIUM", cat: "Subscriptions" },
|
|
|
|
// ── ENTERTAINMENT (credit) ──
|
|
{ acct: credit, d: 20, amt: 24.99, desc: "AMC THEATRES TICKET SAN FRANCISCO", cat: "Entertainment" },
|
|
{ acct: credit, d: 85, amt: 189.00, desc: "GOLDEN STATE WARRIORS - CHASE CENTER", cat: "Entertainment" },
|
|
{ acct: credit, d: 130, amt: 95.00, desc: "TICKETMASTER CONCERT TICKETS", cat: "Entertainment" },
|
|
|
|
// ── TRANSPORTATION (credit) ──
|
|
{ acct: credit, d: 2, amt: 18.40, desc: "UBER TRIP SAN FRANCISCO CA", cat: "Transportation" },
|
|
{ acct: credit, d: 6, amt: 22.10, desc: "UBER TRIP SAN FRANCISCO CA", cat: "Transportation" },
|
|
{ acct: credit, d: 13, amt: 15.80, desc: "LYFT RIDE SAN FRANCISCO", cat: "Transportation" },
|
|
{ acct: credit, d: 27, amt: 89.00, desc: "SHELL OIL GAS STATION #4421", cat: "Transportation" },
|
|
{ acct: credit, d: 48, amt: 76.50, desc: "SHELL OIL GAS STATION #4421", cat: "Transportation" },
|
|
{ acct: credit, d: 58, amt: 21.60, desc: "UBER TRIP SAN FRANCISCO CA", cat: "Transportation" },
|
|
{ acct: credit, d: 72, amt: 88.00, desc: "BART CLIPPER CARD RELOAD", cat: "Transportation" },
|
|
|
|
// ── SHOPPING (credit) ──
|
|
{ acct: credit, d: 14, amt: 243.50, desc: "AMAZON.COM PURCHASE", cat: "Shopping" },
|
|
{ acct: credit, d: 30, amt: 89.99, desc: "AMAZON.COM PURCHASE", cat: "Shopping" },
|
|
{ acct: credit, d: 53, amt: 1450.00, desc: "APPLE.COM/BILL - IPAD PRO", cat: "Shopping" },
|
|
{ acct: credit, d: 66, amt: 178.00, desc: "NORDSTROM #0234 SF", cat: "Shopping" },
|
|
{ acct: credit, d: 90, amt: 340.00, desc: "BEST BUY #1234 DALY CITY", cat: "Shopping" },
|
|
{ acct: credit, d: 115, amt: 67.50, desc: "AMAZON.COM PURCHASE", cat: "Shopping" },
|
|
|
|
// ── CREDIT CARD PAYMENTS (checking) ──
|
|
{ acct: checking, d: 20, amt: 1800.00, desc: "AMEX AUTOPAY - CREDIT CARD PAYMENT", cat: "Transfer" },
|
|
{ acct: checking, d: 50, amt: 2100.00, desc: "AMEX AUTOPAY - CREDIT CARD PAYMENT", cat: "Transfer" },
|
|
{ acct: checking, d: 80, amt: 1650.00, desc: "AMEX AUTOPAY - CREDIT CARD PAYMENT", cat: "Transfer" },
|
|
{ acct: checking, d: 110, amt: 1950.00, desc: "AMEX AUTOPAY - CREDIT CARD PAYMENT", cat: "Transfer" },
|
|
|
|
// ── SAVINGS TRANSFERS (checking → savings) ──
|
|
{ acct: checking, d: 3, amt: 500.00, desc: "TRANSFER TO ALLY SAVINGS *9012", cat: "Transfer" },
|
|
{ acct: checking, d: 33, amt: 500.00, desc: "TRANSFER TO ALLY SAVINGS *9012", cat: "Transfer" },
|
|
{ acct: checking, d: 63, amt: 500.00, desc: "TRANSFER TO ALLY SAVINGS *9012", cat: "Transfer" },
|
|
];
|
|
|
|
console.log("\n💳 Creating transactions...");
|
|
let txCount = 0;
|
|
for (const [i, tx] of txDataset.entries()) {
|
|
const raw = await prisma.transactionRaw.create({
|
|
data: {
|
|
accountId: tx.acct.id,
|
|
bankTransactionId: `demo-${user.id.slice(0, 8)}-${String(i).padStart(3, "0")}`,
|
|
date: daysAgo(tx.d),
|
|
amount: tx.amt,
|
|
description: tx.desc,
|
|
source: "manual",
|
|
rawPayload: { demo: true },
|
|
},
|
|
});
|
|
await prisma.transactionDerived.create({
|
|
data: {
|
|
rawTransactionId: raw.id,
|
|
userCategory: tx.cat,
|
|
isHidden: false,
|
|
modifiedBy: user.id,
|
|
},
|
|
});
|
|
txCount++;
|
|
}
|
|
console.log(` ✓ ${txCount} transactions created`);
|
|
|
|
// ── Categorization Rules ──────────────────────────────────────────────────
|
|
console.log("\n📋 Creating categorization rules...");
|
|
const rules = [
|
|
{
|
|
name: "Whole Foods → Groceries",
|
|
priority: 10,
|
|
conditions: { operator: "AND", filters: [{ field: "description", op: "contains", value: "WHOLE FOODS" }] },
|
|
actions: [{ type: "setCategory", value: "Groceries" }],
|
|
},
|
|
{
|
|
name: "Trader Joe's → Groceries",
|
|
priority: 10,
|
|
conditions: { operator: "AND", filters: [{ field: "description", op: "contains", value: "TRADER JOE" }] },
|
|
actions: [{ type: "setCategory", value: "Groceries" }],
|
|
},
|
|
{
|
|
name: "Netflix → Subscriptions",
|
|
priority: 10,
|
|
conditions: { operator: "AND", filters: [{ field: "description", op: "contains", value: "NETFLIX" }] },
|
|
actions: [{ type: "setCategory", value: "Subscriptions" }],
|
|
},
|
|
{
|
|
name: "Uber/Lyft → Transportation",
|
|
priority: 10,
|
|
conditions: { operator: "OR", filters: [
|
|
{ field: "description", op: "contains", value: "UBER" },
|
|
{ field: "description", op: "contains", value: "LYFT" },
|
|
]},
|
|
actions: [{ type: "setCategory", value: "Transportation" }],
|
|
},
|
|
{
|
|
name: "Amazon → Shopping",
|
|
priority: 10,
|
|
conditions: { operator: "AND", filters: [{ field: "description", op: "contains", value: "AMAZON" }] },
|
|
actions: [{ type: "setCategory", value: "Shopping" }],
|
|
},
|
|
{
|
|
name: "Payroll → Income",
|
|
priority: 20,
|
|
conditions: { operator: "AND", filters: [{ field: "description", op: "contains", value: "PAYROLL" }] },
|
|
actions: [{ type: "setCategory", value: "Income" }],
|
|
},
|
|
{
|
|
name: "Starbucks → Dining",
|
|
priority: 10,
|
|
conditions: { operator: "AND", filters: [{ field: "description", op: "contains", value: "STARBUCKS" }] },
|
|
actions: [{ type: "setCategory", value: "Dining & Restaurants" }],
|
|
},
|
|
{
|
|
name: "Large purchase note (>$500)",
|
|
priority: 5,
|
|
conditions: { operator: "AND", filters: [{ field: "amount", op: "gt", value: 500 }] },
|
|
actions: [{ type: "addNote", value: "Large purchase — review for tax deduction" }],
|
|
},
|
|
];
|
|
|
|
for (const rule of rules) {
|
|
await prisma.rule.create({
|
|
data: {
|
|
userId: user.id,
|
|
name: rule.name,
|
|
priority: rule.priority,
|
|
conditions: rule.conditions,
|
|
actions: rule.actions,
|
|
isActive: true,
|
|
},
|
|
});
|
|
}
|
|
console.log(` ✓ ${rules.length} rules created`);
|
|
|
|
// ── Draft Tax Return ──────────────────────────────────────────────────────
|
|
console.log("\n📄 Creating draft tax return...");
|
|
await prisma.taxReturn.create({
|
|
data: {
|
|
userId: user.id,
|
|
taxYear: 2025,
|
|
filingType: "individual",
|
|
jurisdictions: ["federal", "CA"],
|
|
status: "draft",
|
|
summary: {
|
|
totalIncome: 69600,
|
|
totalExpenses: 42580,
|
|
netIncome: 27020,
|
|
categories: {
|
|
"Income": 69600.00,
|
|
"Rent & Mortgage": 13200.00,
|
|
"Groceries": 1044.52,
|
|
"Dining & Restaurants": 679.90,
|
|
"Transportation": 411.40,
|
|
"Subscriptions": 165.93,
|
|
"Shopping": 2469.99,
|
|
"Healthcare": 505.00,
|
|
"Utilities": 755.90,
|
|
"Entertainment": 308.99,
|
|
"Transfer": 10500.00,
|
|
},
|
|
},
|
|
},
|
|
});
|
|
console.log(" ✓ Draft 2025 tax return");
|
|
|
|
// ── Audit Log ─────────────────────────────────────────────────────────────
|
|
await prisma.auditLog.create({
|
|
data: { userId: user.id, action: "auth.register", metadata: { email: DEMO_EMAIL, source: "seed-script" } },
|
|
});
|
|
|
|
// ── Summary ───────────────────────────────────────────────────────────────
|
|
console.log(`
|
|
╔══════════════════════════════════════════════╗
|
|
║ Demo Account Ready ║
|
|
╠══════════════════════════════════════════════╣
|
|
║ Email: demo@ledgerone.app ║
|
|
║ Password: Demo1234! ║
|
|
╠══════════════════════════════════════════════╣
|
|
║ Features populated: ║
|
|
║ ✓ Email verified (no confirmation needed) ║
|
|
║ ✓ Pro subscription (30-day period) ║
|
|
║ ✓ 3 accounts (checking/credit/savings) ║
|
|
║ ✓ ${String(txCount).padEnd(3)} transactions (6 months of data) ║
|
|
║ ✓ ${String(rules.length).padEnd(3)} categorization rules ║
|
|
║ ✓ Draft 2025 tax return ║
|
|
║ ✓ 2FA disabled (enable via Settings → 2FA) ║
|
|
╚══════════════════════════════════════════════╝
|
|
`);
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error("❌ Seed failed:", e.message);
|
|
process.exit(1);
|
|
})
|
|
.finally(() => prisma.$disconnect());
|