Files
hwinkel 29619658fc
Build and Push Docker Image / build (push) Failing after 1m35s
feat: add graphify plugin and documentation
- Introduced a new graphify OpenCode plugin to remind users about the knowledge graph before executing bash commands.
- Added AGENTS.md for agent guidance, including development commands, environment setup, Docker deployment, code structure, conventions, and testing instructions.
- Created opencode.json to configure the graphify plugin and superpowers.
- Updated tests to improve type safety and added missing imports in test files.
- Added .graphify_uncached.txt to track relevant files for graphify.
2026-05-09 11:52:16 +02:00

153 lines
5.3 KiB
TypeScript

import { describe, it, expect } from "vitest";
describe("Buchungen - Double-Entry Bookkeeping Logic", () => {
describe("TransactionAccount Enum", () => {
it("should have KASSE and BANK accounts", () => {
// These are the two main accounts for German bookkeeping
const accounts = ["KASSE", "BANK"];
expect(accounts).toContain("KASSE");
expect(accounts).toContain("BANK");
});
});
describe("TransactionType Enum", () => {
it("should have EINLAGE and ENTNAHME types", () => {
// EINLAGE = Owner investment, ENTNAHME = Owner withdrawal
const types = ["EINLAGE", "ENTNAHME"];
expect(types).toContain("EINLAGE");
expect(types).toContain("ENTNAHME");
});
});
describe("Zahlungsart Enum", () => {
it("should have KASSE and BANK payment methods", () => {
const paymentMethods = ["KASSE", "BANK"];
expect(paymentMethods).toContain("KASSE");
expect(paymentMethods).toContain("BANK");
});
});
describe("Buchung Business Logic", () => {
it("should calculate correct sign for EINLAGE (positive)", () => {
// EINLAGE increases the company's assets
const amount = 1000;
const type: "EINLAGE" | "ENTNAHME" = "EINLAGE";
// In German bookkeeping: EINLAGE is recorded as positive (credit to equity)
const signedAmount = type === "EINLAGE" ? amount : -amount;
expect(signedAmount).toBe(1000);
});
it("should calculate correct sign for ENTNAHME (negative)", () => {
// ENTNAHME decreases the company's assets
const amount = 500;
const type: "EINLAGE" | "ENTNAHME" = "ENTNAHME";
// In German bookkeeping: ENTNAHME is recorded as negative (debit to equity)
const signedAmount = type === "EINLAGE" ? amount : -amount;
expect(signedAmount).toBe(-500);
});
it("should identify business records correctly", () => {
// Business records (isBusinessRecord = true) come from Einnahmen/Ausgaben
const isBusinessRecord = true;
const hasKategorie = true;
expect(isBusinessRecord).toBe(true);
expect(hasKategorie).toBe(true);
});
it("should handle non-business records (private)", () => {
// Non-business records might not have a kategorie
const isBusinessRecord = false;
const kategorie = null;
expect(isBusinessRecord).toBe(false);
expect(kategorie).toBeNull();
});
it("should validate Decimal precision for amounts", () => {
// Prisma Decimal(10,2) - max 10 digits, 2 decimal places
const amount = 12345678.90; // 8 digits before decimal, 2 after
const maxAmount = 99999999.99; // Max for DECIMAL(10,2)
expect(amount).toBeLessThanOrEqual(maxAmount);
expect(Number(amount.toFixed(2))).toBe(12345678.9);
});
});
describe("Buchung Link Logic (Linked Transactions)", () => {
it("should allow linking related transactions", () => {
// Example: An invoice payment might be linked to the invoice
const buchungId = "buchung-123";
const linkedBuchungId = "buchung-456";
// A Buchung can be linked to another (e.g., invoice payment -> invoice)
const link = { source: buchungId, target: linkedBuchungId };
expect(link.source).toBe("buchung-123");
expect(link.target).toBe("buchung-456");
});
});
describe("Kategorie Logic", () => {
it("should have unique category names per company", () => {
// BuchungKategorie has @@unique([companyId, name, typ])
const companyId = "company-123";
const categories = [
{ companyId, name: "Fußpflege", typ: "EINNAHME" },
{ companyId, name: "Miete", typ: "AUSGABE" },
];
const uniqueCheck = new Set(categories.map(c => `${c.companyId}-${c.name}-${c.typ}`));
expect(uniqueCheck.size).toBe(categories.length);
});
it("should distinguish between EINNAHME and AUSGABE", () => {
const einnahme: "EINNAHME" = "EINNAHME";
const ausgabe: "AUSGABE" = "AUSGABE";
expect(einnahme).toBe("EINNAHME");
expect(ausgabe).toBe("AUSGABE");
expect(einnahme).not.toBe(ausgabe);
});
});
describe("Date-Based Queries", () => {
it("should filter Buchungen by date range", () => {
const buchungen = [
{ date: new Date("2026-01-15"), amount: 100 },
{ date: new Date("2026-02-20"), amount: 200 },
{ date: new Date("2026-03-10"), amount: 300 },
];
const startDate = new Date("2026-02-01");
const endDate = new Date("2026-03-31");
const filtered = buchungen.filter(b =>
b.date >= startDate && b.date <= endDate
);
expect(filtered).toHaveLength(2);
expect(filtered[0].amount).toBe(200);
expect(filtered[1].amount).toBe(300);
});
it("should group Buchungen by month for reports", () => {
const buchungen = [
{ date: new Date("2026-01-10"), amount: 100 },
{ date: new Date("2026-01-20"), amount: 150 },
{ date: new Date("2026-02-05"), amount: 200 },
];
const grouped = buchungen.reduce((acc, b) => {
const month = b.date.getMonth(); // 0-indexed
acc[month] = (acc[month] || 0) + b.amount;
return acc;
}, {} as Record<number, number>);
expect(grouped[0]).toBe(250); // January (month 0)
expect(grouped[1]).toBe(200); // February (month 1)
});
});
});