feat(seed): örnek cariler ve hareketleri
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,158 @@
|
|||||||
|
import type { Types } from "mongoose";
|
||||||
|
import { createContact, createContactEntry } from "@/features/contacts/service";
|
||||||
|
import { AccountModel } from "@/models/account";
|
||||||
|
import { ContactModel } from "@/models/contact";
|
||||||
|
import type { ContactEntryKind, ContactRelation } from "@/shared/contacts";
|
||||||
|
import { todayIstanbul } from "@/shared/dates";
|
||||||
|
import { formatMoneyInput } from "@/shared/money";
|
||||||
|
import { addDays } from "@/shared/recurrence";
|
||||||
|
import { contactEntrySchema, contactFormSchema } from "@/shared/schemas/contact";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Örnek cariler (Faz 8a): vadesi yaklaşan alacak, kısmen ödenen borç, vadesi geçmiş küçük alacak
|
||||||
|
* ve "yalnızca ben" cari. İdempotent: cari `seedRef` ile; hareketler yalnızca cari ilk
|
||||||
|
* oluşturulduğunda eklenir.
|
||||||
|
*/
|
||||||
|
type SeedContact = {
|
||||||
|
ref: string;
|
||||||
|
household: string;
|
||||||
|
by: string;
|
||||||
|
name: string;
|
||||||
|
relation: ContactRelation;
|
||||||
|
phone?: string;
|
||||||
|
isPrivate?: boolean;
|
||||||
|
dueInDays?: number;
|
||||||
|
reminderDays?: number;
|
||||||
|
entries: {
|
||||||
|
kind: ContactEntryKind;
|
||||||
|
account: string;
|
||||||
|
amountMinor: number;
|
||||||
|
daysAgo: number;
|
||||||
|
description?: string;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const CONTACTS: SeedContact[] = [
|
||||||
|
{
|
||||||
|
ref: "ahmet",
|
||||||
|
household: "Çakır Ailesi",
|
||||||
|
by: "admin",
|
||||||
|
name: "Ahmet Yılmaz",
|
||||||
|
relation: "friend",
|
||||||
|
phone: "0532 000 00 01",
|
||||||
|
dueInDays: 5,
|
||||||
|
reminderDays: 7,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
kind: "lend",
|
||||||
|
account: "Ziraat Maaş",
|
||||||
|
amountMinor: 5_000_00,
|
||||||
|
daysAgo: 70,
|
||||||
|
description: "Araba tamiri için",
|
||||||
|
},
|
||||||
|
{ kind: "collect", account: "Cüzdan", amountMinor: 2_000_00, daysAgo: 30 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "hasan",
|
||||||
|
household: "Çakır Ailesi",
|
||||||
|
by: "admin",
|
||||||
|
name: "Hasan Çakır",
|
||||||
|
relation: "family",
|
||||||
|
dueInDays: 60,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
kind: "borrow",
|
||||||
|
account: "Ziraat Maaş",
|
||||||
|
amountMinor: 20_000_00,
|
||||||
|
daysAgo: 120,
|
||||||
|
description: "Ev eşyası için",
|
||||||
|
},
|
||||||
|
{ kind: "repay", account: "Ziraat Maaş", amountMinor: 5_000_00, daysAgo: 60 },
|
||||||
|
{ kind: "repay", account: "Ziraat Maaş", amountMinor: 5_000_00, daysAgo: 10 },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "elif",
|
||||||
|
household: "Çakır Ailesi",
|
||||||
|
by: "ayse",
|
||||||
|
name: "Elif (komşu)",
|
||||||
|
relation: "neighbor",
|
||||||
|
dueInDays: -2,
|
||||||
|
entries: [
|
||||||
|
{
|
||||||
|
kind: "lend",
|
||||||
|
account: "Cüzdan",
|
||||||
|
amountMinor: 300_00,
|
||||||
|
daysAgo: 12,
|
||||||
|
description: "Pazar alışverişi",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "selin",
|
||||||
|
household: "Çakır Ailesi",
|
||||||
|
by: "ayse",
|
||||||
|
name: "Selin (iş arkadaşı)",
|
||||||
|
relation: "work",
|
||||||
|
isPrivate: true,
|
||||||
|
entries: [{ kind: "lend", account: "Cüzdan", amountMinor: 1_000_00, daysAgo: 8 }],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ref: "ali",
|
||||||
|
household: "Annemler",
|
||||||
|
by: "fatma",
|
||||||
|
name: "Ali Demir",
|
||||||
|
relation: "family",
|
||||||
|
dueInDays: 45,
|
||||||
|
entries: [{ kind: "borrow", account: "Vakıfbank Emekli", amountMinor: 3_000_00, daysAgo: 25 }],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export async function seedContacts(
|
||||||
|
householdIds: Map<string, Types.ObjectId>,
|
||||||
|
userIds: Map<string, string>,
|
||||||
|
): Promise<number> {
|
||||||
|
const today = todayIstanbul();
|
||||||
|
let created = 0;
|
||||||
|
for (const c of CONTACTS) {
|
||||||
|
const householdId = householdIds.get(c.household)!;
|
||||||
|
const seedRef = `contact:${c.ref}`;
|
||||||
|
if (await ContactModel.exists({ householdId, seedRef })) continue;
|
||||||
|
const by = userIds.get(c.by)!;
|
||||||
|
const oldest = Math.max(...c.entries.map((e) => e.daysAgo));
|
||||||
|
const r = await createContact(
|
||||||
|
by,
|
||||||
|
String(householdId),
|
||||||
|
contactFormSchema.parse({
|
||||||
|
name: c.name,
|
||||||
|
relation: c.relation,
|
||||||
|
phone: c.phone ?? "",
|
||||||
|
openingDate: addDays(today, -oldest - 1),
|
||||||
|
isPrivate: c.isPrivate ?? false,
|
||||||
|
dueDate: c.dueInDays !== undefined ? addDays(today, c.dueInDays) : null,
|
||||||
|
reminderDays: c.reminderDays ?? 3,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (!r.ok) throw new Error(`Seed carisi oluşturulamadı (${c.name}): ${r.error}`);
|
||||||
|
await ContactModel.updateOne({ _id: r.data.id }, { $set: { seedRef } });
|
||||||
|
for (const e of c.entries) {
|
||||||
|
const account = await AccountModel.findOne({ householdId, name: e.account }).lean();
|
||||||
|
const x = await createContactEntry(
|
||||||
|
by,
|
||||||
|
String(householdId),
|
||||||
|
r.data.id,
|
||||||
|
contactEntrySchema.parse({
|
||||||
|
kind: e.kind,
|
||||||
|
accountId: String(account!._id),
|
||||||
|
amount: formatMoneyInput(e.amountMinor),
|
||||||
|
date: addDays(today, -e.daysAgo),
|
||||||
|
description: e.description ?? "",
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
if (!x.ok) throw new Error(`Seed cari hareketi oluşturulamadı (${c.name}): ${x.error}`);
|
||||||
|
}
|
||||||
|
created++;
|
||||||
|
}
|
||||||
|
return created;
|
||||||
|
}
|
||||||
@@ -47,6 +47,7 @@ describe("seed", () => {
|
|||||||
transactions: 0,
|
transactions: 0,
|
||||||
recurringRules: 0,
|
recurringRules: 0,
|
||||||
installmentPlans: 0,
|
installmentPlans: 0,
|
||||||
|
contacts: 0,
|
||||||
});
|
});
|
||||||
expect(await getDb().collection("user").countDocuments()).toBe(SEED_USERS.length + 1);
|
expect(await getDb().collection("user").countDocuments()).toBe(SEED_USERS.length + 1);
|
||||||
expect(await HouseholdModel.countDocuments()).toBe(SEED_HOUSEHOLDS.length);
|
expect(await HouseholdModel.countDocuments()).toBe(SEED_HOUSEHOLDS.length);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import { HouseholdModel } from "@/models/household";
|
|||||||
import { HouseholdMemberModel } from "@/models/household-member";
|
import { HouseholdMemberModel } from "@/models/household-member";
|
||||||
import { storedEmail } from "@/shared/account-email";
|
import { storedEmail } from "@/shared/account-email";
|
||||||
import { runMigrations } from "../migrations";
|
import { runMigrations } from "../migrations";
|
||||||
|
import { seedContacts } from "./contacts";
|
||||||
import { seedInstallments } from "./installments";
|
import { seedInstallments } from "./installments";
|
||||||
import { seedRecurring } from "./recurring";
|
import { seedRecurring } from "./recurring";
|
||||||
import { seedTransactions } from "./transactions";
|
import { seedTransactions } from "./transactions";
|
||||||
@@ -47,6 +48,7 @@ export type SeedSummary = {
|
|||||||
transactions: number;
|
transactions: number;
|
||||||
recurringRules: number;
|
recurringRules: number;
|
||||||
installmentPlans: number;
|
installmentPlans: number;
|
||||||
|
contacts: number;
|
||||||
};
|
};
|
||||||
accounts: SeedAccountRow[];
|
accounts: SeedAccountRow[];
|
||||||
};
|
};
|
||||||
@@ -64,6 +66,7 @@ const DOMAIN_COLLECTIONS = [
|
|||||||
"reconciliations",
|
"reconciliations",
|
||||||
"recurringRules",
|
"recurringRules",
|
||||||
"statements",
|
"statements",
|
||||||
|
"contacts",
|
||||||
];
|
];
|
||||||
|
|
||||||
type AuthApi = ReturnType<typeof getAuth>["api"];
|
type AuthApi = ReturnType<typeof getAuth>["api"];
|
||||||
@@ -115,6 +118,7 @@ export async function runSeed({
|
|||||||
transactions: 0,
|
transactions: 0,
|
||||||
recurringRules: 0,
|
recurringRules: 0,
|
||||||
installmentPlans: 0,
|
installmentPlans: 0,
|
||||||
|
contacts: 0,
|
||||||
},
|
},
|
||||||
accounts: [],
|
accounts: [],
|
||||||
};
|
};
|
||||||
@@ -289,6 +293,8 @@ export async function runSeed({
|
|||||||
);
|
);
|
||||||
if (summary.created.installmentPlans)
|
if (summary.created.installmentPlans)
|
||||||
log(`✓ Örnek taksitli alışverişler: ${summary.created.installmentPlans}`);
|
log(`✓ Örnek taksitli alışverişler: ${summary.created.installmentPlans}`);
|
||||||
|
summary.created.contacts = await seedContacts(householdIds, userIdMap);
|
||||||
|
if (summary.created.contacts) log(`✓ Örnek cariler: ${summary.created.contacts}`);
|
||||||
|
|
||||||
// 7) Geliştirme hesapları tablosu
|
// 7) Geliştirme hesapları tablosu
|
||||||
const all = [
|
const all = [
|
||||||
|
|||||||
Reference in New Issue
Block a user