"use server"; import { revalidatePath } from "next/cache"; import { AppwriteException, ID } from "node-appwrite"; import { z } from "zod"; import { logAudit } from "./audit"; import { isPlanLimitError, planLimitMessage, requirePlanCapacity, } from "./plan-limits"; import { DATABASE_ID, TABLES, type FinanceEntry } from "./schema"; import { canAccessRow, scopedRowPermissions } from "./scope-permissions"; import { createAdminClient } from "./server"; import { requireTenant } from "./tenant-guard"; import type { FinanceActionState } from "./finance-types"; import { financeEntrySchema } from "@/lib/validation/finance"; function appwriteError(e: unknown): string { if (e instanceof AppwriteException) return e.message || "Beklenmeyen hata."; return "Bağlantı hatası. Tekrar deneyin."; } function flattenErrors(err: z.ZodError): Record { const out: Record = {}; for (const issue of err.issues) { const key = issue.path.join("."); if (key && !out[key]) out[key] = issue.message; } return out; } function pickFormFields(formData: FormData) { return { type: formData.get("type") as "income" | "expense" | "debt" | "receivable", amount: String(formData.get("amount") ?? "0"), date: String(formData.get("date") ?? ""), description: String(formData.get("description") ?? "").trim(), customerId: String(formData.get("customerId") ?? ""), invoiceId: String(formData.get("invoiceId") ?? ""), paymentMethod: formData.get("paymentMethod") as | "cash" | "transfer" | "card" | "check" | "other" | null, bankAccountId: String(formData.get("bankAccountId") ?? ""), scope: (formData.get("scope") as "company" | "personal" | null) ?? "company", }; } function toIso(v: string): string { if (!v) return v; if (/^\d{4}-\d{2}-\d{2}$/.test(v)) return `${v}T00:00:00.000+00:00`; return v; } export async function createFinanceEntryAction( _prev: FinanceActionState, formData: FormData, ): Promise { let ctx; try { ctx = await requireTenant(); } catch { return { ok: false, error: "Yetkiniz yok." }; } const parsed = financeEntrySchema.safeParse(pickFormFields(formData)); if (!parsed.success) { return { ok: false, error: "Form geçersiz.", fieldErrors: flattenErrors(parsed.error) }; } try { await requirePlanCapacity(ctx, "financeEntries"); } catch (e) { if (isPlanLimitError(e)) { return { ok: false, error: planLimitMessage(e.resource, e.limit), code: "PLAN_LIMIT_EXCEEDED", }; } throw e; } try { const { tablesDB } = createAdminClient(); const data = { ...parsed.data, date: toIso(parsed.data.date) }; const row = await tablesDB.createRow( DATABASE_ID, TABLES.financeEntries, ID.unique(), { tenantId: ctx.tenantId, createdBy: ctx.user.id, ...data, }, scopedRowPermissions(ctx.tenantId, ctx.user.id, parsed.data.scope), ); await logAudit({ tenantId: ctx.tenantId, userId: ctx.user.id, action: "create", entityType: "finance_entry", entityId: row.$id, changes: data, }); } catch (e) { return { ok: false, error: appwriteError(e) }; } revalidatePath("/finance"); return { ok: true }; } export async function updateFinanceEntryAction( _prev: FinanceActionState, formData: FormData, ): Promise { const id = String(formData.get("id") ?? ""); if (!id) return { ok: false, error: "ID eksik." }; let ctx; try { ctx = await requireTenant(); } catch { return { ok: false, error: "Yetkiniz yok." }; } const parsed = financeEntrySchema.safeParse(pickFormFields(formData)); if (!parsed.success) { return { ok: false, error: "Form geçersiz.", fieldErrors: flattenErrors(parsed.error) }; } try { const { tablesDB } = createAdminClient(); const existing = (await tablesDB.getRow( DATABASE_ID, TABLES.financeEntries, id, )) as unknown as FinanceEntry; if (existing.tenantId !== ctx.tenantId || !canAccessRow(existing, ctx.user.id)) { return { ok: false, error: "Erişim engellendi." }; } const data = { ...parsed.data, date: toIso(parsed.data.date) }; await tablesDB.updateRow( DATABASE_ID, TABLES.financeEntries, id, data, scopedRowPermissions(ctx.tenantId, existing.createdBy, parsed.data.scope), ); await logAudit({ tenantId: ctx.tenantId, userId: ctx.user.id, action: "update", entityType: "finance_entry", entityId: id, changes: data, }); } catch (e) { return { ok: false, error: appwriteError(e) }; } revalidatePath("/finance"); return { ok: true }; } export async function deleteFinanceEntryAction( formData: FormData, ): Promise { const id = String(formData.get("id") ?? ""); if (!id) return { ok: false, error: "ID eksik." }; let ctx; try { ctx = await requireTenant(); } catch { return { ok: false, error: "Yetkiniz yok." }; } try { const { tablesDB } = createAdminClient(); const existing = (await tablesDB.getRow( DATABASE_ID, TABLES.financeEntries, id, )) as unknown as FinanceEntry; if (existing.tenantId !== ctx.tenantId || !canAccessRow(existing, ctx.user.id)) { return { ok: false, error: "Erişim engellendi." }; } await tablesDB.deleteRow(DATABASE_ID, TABLES.financeEntries, id); await logAudit({ tenantId: ctx.tenantId, userId: ctx.user.id, action: "delete", entityType: "finance_entry", entityId: id, changes: { type: existing.type, amount: existing.amount }, }); } catch (e) { return { ok: false, error: appwriteError(e) }; } revalidatePath("/finance"); return { ok: true }; }