feat(reports): rapor ve dashboard grafiği motorları

- periyot aralıkları (haftalık, aylık, altı aylık, yıllık) ve kümülatif dönüşüm
- kategori payı, alt kategori kırılımı, önceki aya göre değişim, tasarruf oranı
- kategori, trend, kişi/hesap ve bütçe raporu servisleri; gizli hesap kuralları; testler

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 00:29:25 +03:00
parent d77a2efcb8
commit bc2a8a3bde
9 changed files with 1050 additions and 0 deletions
+53
View File
@@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";
import { cumulative, overviewRange, weekday } from "./overview";
describe("overviewRange", () => {
it("haftalık: Pazartesi–Pazar, bugünü içerir", () => {
// 2026-11-07 Cumartesi.
expect(weekday("2026-11-07")).toBe(5);
const r = overviewRange("week", "2026-11-07");
expect(r).toMatchObject({ granularity: "day", from: "2026-11-02", to: "2026-11-08" });
expect(r.buckets).toHaveLength(7);
// Ay sınırını aşan hafta.
expect(overviewRange("week", "2026-12-01").from).toBe("2026-11-30");
});
it("aylık: bütçe ayının günleri (başlangıç günü 15)", () => {
const r = overviewRange("month", "2026-11-07", 15);
expect(r.from).toBe("2026-10-15");
expect(r.to).toBe("2026-11-14");
expect(r.buckets).toHaveLength(31);
expect(overviewRange("month", "2026-11-07").buckets).toHaveLength(30);
});
it("altı aylık ve yıllık: bu ay dahil son 6 / 12 bütçe ayı", () => {
const half = overviewRange("half", "2026-11-07");
expect(half).toMatchObject({ granularity: "month", from: "2026-06-01", to: "2026-11-30" });
expect(half.buckets).toEqual([
"2026-06",
"2026-07",
"2026-08",
"2026-09",
"2026-10",
"2026-11",
]);
const year = overviewRange("year", "2026-01-20", 15);
expect(year.buckets[0]).toBe("2025-02");
expect(year.buckets.at(-1)).toBe("2026-01");
});
});
describe("cumulative", () => {
it("seriler ayrı birikir; boş nokta boş kalır, toplamı bozmaz", () => {
const out = cumulative([
{ key: "a", plannedIncome: 100, budget: 50, actual: 10 },
{ key: "b", plannedIncome: 0, budget: null, actual: 20 },
{ key: "c", plannedIncome: 50, budget: 30, actual: null },
]);
expect(out.map((p) => [p.plannedIncome, p.budget, p.actual])).toEqual([
[100, 50, 10],
[100, null, 30],
[150, 80, null],
]);
});
});