feat(recurring): düzenli işlem kuralları, planlı kayıt üretimi, onay/atla/ertele ve seri düzenleme

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-05 10:55:05 +03:00
parent eaafe5dd9b
commit 04a17d2775
13 changed files with 1493 additions and 7 deletions
+95
View File
@@ -0,0 +1,95 @@
import { describe, expect, it } from "vitest";
import {
addDays,
addMonths,
describeRecurrence,
isFinished,
listOccurrences,
occurrenceDate,
type Recurrence,
} from "./recurrence";
const rule = (over: Partial<Recurrence>): Recurrence => ({
frequency: "monthly",
interval: 1,
monthlyMode: "day",
startDate: "2026-01-31",
endDate: null,
count: null,
...over,
});
describe("tarih aritmetiği", () => {
it("gün ve ay ekleme, ay sonu taşması", () => {
expect(addDays("2026-12-30", 3)).toBe("2027-01-02");
expect(addDays("2028-03-01", -1)).toBe("2028-02-29");
expect(addMonths("2026-01-31", 1)).toBe("2026-02-28");
expect(addMonths("2026-01-31", 1, 31)).toBe("2026-02-28");
expect(addMonths("2026-11-15", 3)).toBe("2027-02-15");
expect(addMonths("2026-04-10", 0, "last")).toBe("2026-04-30");
});
});
describe("occurrenceDate", () => {
it("aylık: başlangıç günü korunur, ayda yoksa son gün", () => {
const r = rule({});
expect([0, 1, 2, 3].map((i) => occurrenceDate(r, i))).toEqual([
"2026-01-31",
"2026-02-28",
"2026-03-31",
"2026-04-30",
]);
});
it("aylık son gün modu (artık yıl dahil)", () => {
const r = rule({ monthlyMode: "last", startDate: "2028-01-10" });
expect([0, 1, 2].map((i) => occurrenceDate(r, i))).toEqual([
"2028-01-31",
"2028-02-29",
"2028-03-31",
]);
});
it("günlük, haftalık (aralıkla), yıllık (29 Şubat)", () => {
expect(
occurrenceDate(rule({ frequency: "daily", interval: 3, startDate: "2026-11-30" }), 1),
).toBe("2026-12-03");
expect(
occurrenceDate(rule({ frequency: "weekly", interval: 2, startDate: "2026-11-02" }), 2),
).toBe("2026-11-30");
const y = rule({ frequency: "yearly", startDate: "2028-02-29" });
expect([1, 4].map((i) => occurrenceDate(y, i))).toEqual(["2029-02-28", "2032-02-29"]);
});
});
describe("listOccurrences", () => {
it("tekrar sayısı, bitiş tarihi ve ufuk sınırları", () => {
const r = rule({ startDate: "2026-01-05", count: 3 });
expect(listOccurrences(r, "2030-01-01").map((o) => o.date)).toEqual([
"2026-01-05",
"2026-02-05",
"2026-03-05",
]);
expect(isFinished(r, 3)).toBe(true);
const e = rule({ startDate: "2026-01-05", endDate: "2026-02-10" });
expect(listOccurrences(e, "2030-01-01")).toHaveLength(2);
const h = rule({ startDate: "2026-01-05" });
expect(listOccurrences(h, "2026-04-05").map((o) => o.index)).toEqual([0, 1, 2, 3]);
expect(listOccurrences(h, "2026-04-05", 2).map((o) => o.index)).toEqual([2, 3]);
expect(isFinished(h, 100)).toBe(false);
});
});
describe("describeRecurrence", () => {
it("Türkçe açıklama", () => {
expect(describeRecurrence(rule({ startDate: "2026-11-03" }))).toBe("Her ay 3. gün");
expect(describeRecurrence(rule({ monthlyMode: "last" }))).toBe("Her ayın son günü");
expect(
describeRecurrence(rule({ frequency: "weekly", interval: 2, startDate: "2026-11-02" })),
).toBe("2 haftada bir Pazartesi");
expect(describeRecurrence(rule({ frequency: "yearly", startDate: "2026-03-15" }))).toBe(
"Her yıl 15 Mart",
);
expect(describeRecurrence(rule({ frequency: "daily" }))).toBe("Her gün");
});
});