Files
isletmem-kovakcrm/src/app/(dashboard)/finance/cards/page.tsx
T
kovakmedya 121fbdba9d feat(banking C): credit cards + monthly statements
Final banking step. User-managed: each month, paste your bank statement
totals into the form. We don't auto-compute interest — bank's number is
authoritative.

Schema:
- credit_cards: bankName, cardName, last4, creditLimit, statementDay,
  dueDay, interestRate (monthly nominal %), bankAccountId (optional FK,
  payments expense from this account), archived, notes.
- credit_card_statements: cardId, period (YYYY-MM), statementDate,
  dueDate, totalDebt, minimumPayment, paidAmount, status
  (pending/partial/paid/overdue), financeEntryId (link to last payment).
- Indexes on (tenantId, archived) for cards and (tenantId, cardId) +
  (tenantId, status, dueDate) for statements.

Server (lib/appwrite/credit-card-actions.ts):
- create/update/archive(toggle)/delete for cards. Card delete cascades
  through statements + their finance_entries.
- createStatementAction: computes status from dueDate + paidAmount.
- payStatementAction: partial-payment friendly. Creates a single
  finance_entry expense for the pay amount, capped at remaining balance,
  bankAccountId carried from the card. Recomputes status: paid (full),
  partial (some + pre-due), overdue (past due with anything < total).
- deleteStatementAction: removes linked finance_entry too.
- All audit-logged.

UI (/finance/cards):
- 3 stat cards: aktif kart sayısı, bekleyen toplam, vadesi geçmiş ekstre.
- Per-card panel: bank/name/last4, limit + statement/due day + monthly
  interest, current outstanding. Statement table inside card with status
  badges, Öde button (opens partial-payment dialog), delete button.
- CardFormSheet for card CRUD, StatementFormSheet for statement creation
  with default period/dates derived from card's statementDay/dueDay.

Sidebar Finans submenu now functional: Banka hesapları → /finance/banks,
Krediler → /finance/loans, Kredi kartları → /finance/cards.
2026-04-30 07:36:31 +03:00

81 lines
2.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { Metadata } from "next";
import { redirect } from "next/navigation";
import { listBankAccounts } from "@/lib/appwrite/bank-account-queries";
import {
listCreditCards,
listStatements,
} from "@/lib/appwrite/credit-card-queries";
import { requireTenant } from "@/lib/appwrite/tenant-guard";
import { CardsClient } from "./components/cards-client";
export const metadata: Metadata = {
title: "İşletmem — Kredi kartları",
};
export default async function CardsPage() {
let ctx;
try {
ctx = await requireTenant();
} catch {
redirect("/onboarding");
}
const [cards, statements, bankAccounts] = await Promise.all([
listCreditCards(ctx.tenantId),
listStatements(ctx.tenantId),
listBankAccounts(ctx.tenantId),
]);
const bankMap = new Map(
bankAccounts.map((b) => [b.$id, `${b.bankName}${b.accountName}`]),
);
return (
<div className="flex-1 space-y-6 px-6 pt-0">
<div className="flex flex-col gap-1">
<p className="text-muted-foreground text-sm">{ctx.settings?.companyName ?? "Çalışma alanı"}</p>
<h1 className="text-2xl font-bold tracking-tight">Kredi kartları</h1>
<p className="text-muted-foreground text-sm">
Kartlarınızı ve aylık ekstrelerinizi takip edin. Ekstre ödendiğinde otomatik gider kaydı oluşur.
</p>
</div>
<CardsClient
cards={cards.map((c) => ({
id: c.$id,
bankName: c.bankName,
cardName: c.cardName,
last4: c.last4 ?? "",
creditLimit: c.creditLimit ?? 0,
statementDay: c.statementDay ?? 1,
dueDay: c.dueDay ?? 10,
interestRate: c.interestRate ?? 4.25,
bankAccountId: c.bankAccountId ?? "",
bankAccountLabel: c.bankAccountId ? bankMap.get(c.bankAccountId) ?? "" : "",
archived: Boolean(c.archived),
notes: c.notes ?? "",
}))}
statements={statements.map((s) => ({
id: s.$id,
cardId: s.cardId,
period: s.period,
statementDate: s.statementDate,
dueDate: s.dueDate,
totalDebt: s.totalDebt,
minimumPayment: s.minimumPayment ?? 0,
paidAmount: s.paidAmount ?? 0,
status: s.status ?? "pending",
notes: s.notes ?? "",
}))}
bankAccounts={bankAccounts
.filter((b) => !b.archived)
.map((b) => ({
id: b.$id,
label: `${b.bankName}${b.accountName}`,
}))}
/>
</div>
);
}