feat(seed): varsayılan kategoriler, örnek hesaplar ve 002 migration
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
import type { HouseholdRole } from "@/features/households/types";
|
||||
import type { AccountType, MealCardProvider } from "@/shared/accounts";
|
||||
import type { IconKey, Swatch } from "@/shared/appearance";
|
||||
import type { SystemRole } from "@/shared/schemas/common";
|
||||
|
||||
/** Örnek (geliştirme) kullanıcıları. E-postası olmayanlar kullanıcı adıyla giriş yapar. */
|
||||
@@ -56,3 +58,97 @@ export const SEED_HOUSEHOLDS: SeedHousehold[] = [
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
/** Örnek hesaplar (tutarlar kuruş; kart borcu negatif). Açılış: 6 ay önce. */
|
||||
export type SeedAccount = {
|
||||
household: string;
|
||||
name: string;
|
||||
type: AccountType;
|
||||
institution?: string;
|
||||
openingBalanceMinor: number;
|
||||
color: Swatch;
|
||||
icon: IconKey;
|
||||
owner?: string;
|
||||
isPrivate?: boolean;
|
||||
mealCardProvider?: MealCardProvider;
|
||||
overdraft?: { limitMinor: number; monthlyRateBp: number; statementDay: number };
|
||||
creditCard?: {
|
||||
limitMinor: number;
|
||||
statementDay: number;
|
||||
dueDays: number;
|
||||
paymentAccount: string;
|
||||
};
|
||||
};
|
||||
|
||||
export const SEED_ACCOUNTS: SeedAccount[] = [
|
||||
{
|
||||
household: "Çakır Ailesi",
|
||||
name: "Cüzdan",
|
||||
type: "cash",
|
||||
openingBalanceMinor: 2_350_00,
|
||||
color: "green",
|
||||
icon: "wallet",
|
||||
},
|
||||
{
|
||||
household: "Çakır Ailesi",
|
||||
name: "Ziraat Maaş",
|
||||
type: "bank",
|
||||
institution: "Ziraat Bankası",
|
||||
openingBalanceMinor: 18_420_50,
|
||||
color: "red",
|
||||
icon: "landmark",
|
||||
overdraft: { limitMinor: 10_000_00, monthlyRateBp: 425, statementDay: 1 },
|
||||
},
|
||||
{
|
||||
household: "Çakır Ailesi",
|
||||
name: "Garanti Bonus",
|
||||
type: "credit_card",
|
||||
institution: "Garanti BBVA",
|
||||
openingBalanceMinor: -6_480_75,
|
||||
color: "teal",
|
||||
icon: "credit-card",
|
||||
creditCard: {
|
||||
limitMinor: 60_000_00,
|
||||
statementDay: 29,
|
||||
dueDays: 10,
|
||||
paymentAccount: "Ziraat Maaş",
|
||||
},
|
||||
},
|
||||
{
|
||||
household: "Çakır Ailesi",
|
||||
name: "Birikim (Vadeli)",
|
||||
type: "savings",
|
||||
institution: "Ziraat Bankası",
|
||||
openingBalanceMinor: 45_000_00,
|
||||
color: "indigo",
|
||||
icon: "piggy-bank",
|
||||
},
|
||||
{
|
||||
household: "Çakır Ailesi",
|
||||
name: "Ayşe — Pluxee",
|
||||
type: "meal_card",
|
||||
mealCardProvider: "pluxee",
|
||||
openingBalanceMinor: 3_120_00,
|
||||
color: "lime",
|
||||
icon: "utensils",
|
||||
owner: "ayse",
|
||||
isPrivate: true,
|
||||
},
|
||||
{
|
||||
household: "Annemler",
|
||||
name: "Nakit",
|
||||
type: "cash",
|
||||
openingBalanceMinor: 1_500_00,
|
||||
color: "green",
|
||||
icon: "wallet",
|
||||
},
|
||||
{
|
||||
household: "Annemler",
|
||||
name: "Vakıfbank Emekli",
|
||||
type: "bank",
|
||||
institution: "VakıfBank",
|
||||
openingBalanceMinor: 27_800_00,
|
||||
color: "amber",
|
||||
icon: "landmark",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -24,18 +24,26 @@ describe("seed", () => {
|
||||
it("ilk çalıştırmada admin, örnek kullanıcılar, haneler ve üyelikleri oluşturur", async () => {
|
||||
const summary = await runSeed({ log: silent });
|
||||
const memberCount = SEED_HOUSEHOLDS.reduce((n, h) => n + h.members.length, 0);
|
||||
expect(summary.created).toEqual({
|
||||
expect(summary.created).toMatchObject({
|
||||
users: SEED_USERS.length + 1,
|
||||
households: SEED_HOUSEHOLDS.length,
|
||||
memberships: memberCount,
|
||||
});
|
||||
expect(summary.created.categories).toBeGreaterThan(40);
|
||||
expect(summary.created.accounts).toBe(7);
|
||||
expect(await getDb().collection("user").countDocuments()).toBe(SEED_USERS.length + 1);
|
||||
expect(await HouseholdMemberModel.countDocuments()).toBe(memberCount);
|
||||
});
|
||||
|
||||
it("idempotenttir: ikinci çalıştırma kopya oluşturmaz", async () => {
|
||||
const summary = await runSeed({ log: silent });
|
||||
expect(summary.created).toEqual({ users: 0, households: 0, memberships: 0 });
|
||||
expect(summary.created).toEqual({
|
||||
users: 0,
|
||||
households: 0,
|
||||
memberships: 0,
|
||||
categories: 0,
|
||||
accounts: 0,
|
||||
});
|
||||
expect(await getDb().collection("user").countDocuments()).toBe(SEED_USERS.length + 1);
|
||||
expect(await HouseholdModel.countDocuments()).toBe(SEED_HOUSEHOLDS.length);
|
||||
});
|
||||
|
||||
+71
-5
@@ -3,11 +3,19 @@ import { getAuth } from "@/lib/auth";
|
||||
import { getDb } from "@/lib/db/mongo";
|
||||
import { connectMongoose } from "@/lib/db/mongoose";
|
||||
import { env } from "@/lib/env";
|
||||
import { installDefaultCategories } from "@/features/categories/service";
|
||||
import { AccountModel } from "@/models/account";
|
||||
import { HouseholdModel } from "@/models/household";
|
||||
import { HouseholdMemberModel } from "@/models/household-member";
|
||||
import { storedEmail } from "@/shared/account-email";
|
||||
import { runMigrations } from "../migrations";
|
||||
import { ADMIN_USERNAME, SEED_HOUSEHOLDS, SEED_USERS, type SeedUser } from "./fixtures";
|
||||
import {
|
||||
ADMIN_USERNAME,
|
||||
SEED_ACCOUNTS,
|
||||
SEED_HOUSEHOLDS,
|
||||
SEED_USERS,
|
||||
type SeedUser,
|
||||
} from "./fixtures";
|
||||
|
||||
/**
|
||||
* İdempotent seed. Kullanıcılar Better Auth API'siyle oluşturulur (şifreler doğru hashlenir);
|
||||
@@ -26,12 +34,24 @@ export type SeedAccountRow = {
|
||||
};
|
||||
|
||||
export type SeedSummary = {
|
||||
created: { users: number; households: number; memberships: number };
|
||||
created: {
|
||||
users: number;
|
||||
households: number;
|
||||
memberships: number;
|
||||
categories: number;
|
||||
accounts: number;
|
||||
};
|
||||
accounts: SeedAccountRow[];
|
||||
};
|
||||
|
||||
const AUTH_COLLECTIONS = ["user", "account", "session", "verification", "rateLimit"];
|
||||
const DOMAIN_COLLECTIONS = ["households", "householdMembers", "auditLogs"];
|
||||
const DOMAIN_COLLECTIONS = [
|
||||
"households",
|
||||
"householdMembers",
|
||||
"auditLogs",
|
||||
"categories",
|
||||
"accounts",
|
||||
];
|
||||
|
||||
type AuthApi = ReturnType<typeof getAuth>["api"];
|
||||
|
||||
@@ -73,7 +93,7 @@ export async function runSeed({
|
||||
const db = getDb();
|
||||
await connectMongoose();
|
||||
const summary: SeedSummary = {
|
||||
created: { users: 0, households: 0, memberships: 0 },
|
||||
created: { users: 0, households: 0, memberships: 0, categories: 0, accounts: 0 },
|
||||
accounts: [],
|
||||
};
|
||||
|
||||
@@ -180,7 +200,53 @@ export async function runSeed({
|
||||
}
|
||||
}
|
||||
|
||||
// 5) Geliştirme hesapları tablosu
|
||||
// 5) Varsayılan kategoriler ve örnek hesaplar
|
||||
const householdIds = new Map<string, Types.ObjectId>();
|
||||
for (const h of SEED_HOUSEHOLDS) {
|
||||
const doc = await HouseholdModel.findOne({ name: h.name }).select({ _id: 1 }).lean();
|
||||
householdIds.set(h.name, doc!._id);
|
||||
summary.created.categories += await installDefaultCategories(doc!._id, actor);
|
||||
}
|
||||
const openingDate = new Date(Date.now() - 183 * 24 * 60 * 60 * 1000);
|
||||
for (const [order, a] of SEED_ACCOUNTS.entries()) {
|
||||
const householdId = householdIds.get(a.household)!;
|
||||
if (await AccountModel.exists({ householdId, name: a.name })) continue;
|
||||
const payer = a.creditCard
|
||||
? await AccountModel.findOne({ householdId, name: a.creditCard.paymentAccount })
|
||||
.select({ _id: 1 })
|
||||
.lean()
|
||||
: null;
|
||||
await AccountModel.create({
|
||||
householdId,
|
||||
name: a.name,
|
||||
type: a.type,
|
||||
institution: a.institution ?? "",
|
||||
mealCardProvider: a.mealCardProvider ?? null,
|
||||
openingBalanceMinor: a.openingBalanceMinor,
|
||||
openingDate,
|
||||
color: a.color,
|
||||
icon: a.icon,
|
||||
order,
|
||||
ownerUserId: a.owner ? new Types.ObjectId(ids.get(a.owner)!) : null,
|
||||
isPrivate: a.isPrivate ?? false,
|
||||
overdraft: a.overdraft ? { enabled: true, ...a.overdraft } : null,
|
||||
creditCard: a.creditCard
|
||||
? {
|
||||
limitMinor: a.creditCard.limitMinor,
|
||||
statementDay: a.creditCard.statementDay,
|
||||
dueRule: { kind: "daysAfter", value: a.creditCard.dueDays },
|
||||
paymentAccountId: payer?._id ?? null,
|
||||
}
|
||||
: null,
|
||||
createdBy: actor,
|
||||
updatedBy: actor,
|
||||
});
|
||||
summary.created.accounts++;
|
||||
}
|
||||
if (summary.created.categories) log(`✓ Varsayılan kategoriler: ${summary.created.categories}`);
|
||||
if (summary.created.accounts) log(`✓ Örnek hesaplar: ${summary.created.accounts}`);
|
||||
|
||||
// 6) Geliştirme hesapları tablosu
|
||||
const all = [
|
||||
{ username: ADMIN_USERNAME, name: "Yönetici", email: adminEmail, role: "admin" as const },
|
||||
...SEED_USERS,
|
||||
|
||||
Reference in New Issue
Block a user