diff --git a/src/app/(dashboard)/finance/loans/components/types.ts b/src/app/(dashboard)/finance/loans/components/types.ts
index 5785205..cbca62d 100644
--- a/src/app/(dashboard)/finance/loans/components/types.ts
+++ b/src/app/(dashboard)/finance/loans/components/types.ts
@@ -17,6 +17,7 @@ export type LoanRow = {
paidAmount: number;
remainingCount: number;
nextDue: string | null;
+ scope: "company" | "personal";
};
export type InstallmentRow = {
diff --git a/src/app/(dashboard)/finance/loans/page.tsx b/src/app/(dashboard)/finance/loans/page.tsx
index 47c4bca..b509475 100644
--- a/src/app/(dashboard)/finance/loans/page.tsx
+++ b/src/app/(dashboard)/finance/loans/page.tsx
@@ -19,9 +19,9 @@ export default async function LoansPage() {
}
const [loans, installments, bankAccounts] = await Promise.all([
- listLoans(ctx.tenantId),
- listAllInstallments(ctx.tenantId),
- listBankAccounts(ctx.tenantId),
+ listLoans(ctx.tenantId, ctx.user.id),
+ listAllInstallments(ctx.tenantId, ctx.user.id),
+ listBankAccounts(ctx.tenantId, ctx.user.id),
]);
const bankMap = new Map(
@@ -90,6 +90,7 @@ export default async function LoansPage() {
paidAmount: m.paidAmount,
remainingCount: m.remainingCount,
nextDue: m.nextDue,
+ scope: (l.scope ?? "company") as "company" | "personal",
};
})}
installments={installments.map((i) => ({
diff --git a/src/app/(dashboard)/finance/page.tsx b/src/app/(dashboard)/finance/page.tsx
index 24e8f5b..172280f 100644
--- a/src/app/(dashboard)/finance/page.tsx
+++ b/src/app/(dashboard)/finance/page.tsx
@@ -20,9 +20,9 @@ export default async function FinancePage() {
}
const [entries, customers, bankAccounts] = await Promise.all([
- listFinanceEntries(ctx.tenantId),
+ listFinanceEntries(ctx.tenantId, ctx.user.id),
listCustomers(ctx.tenantId),
- listBankAccounts(ctx.tenantId),
+ listBankAccounts(ctx.tenantId, ctx.user.id),
]);
const customerMap = new Map(customers.map((c) => [c.$id, c.name]));
diff --git a/src/components/finance/scope-toggle.tsx b/src/components/finance/scope-toggle.tsx
new file mode 100644
index 0000000..32d8404
--- /dev/null
+++ b/src/components/finance/scope-toggle.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+import { useState } from "react";
+import { Building2, User } from "lucide-react";
+
+import { Label } from "@/components/ui/label";
+import { cn } from "@/lib/utils";
+
+type Scope = "company" | "personal";
+
+export function ScopeToggle({
+ name = "scope",
+ defaultValue = "company",
+ label = "Kapsam",
+ description,
+}: {
+ name?: string;
+ defaultValue?: Scope;
+ label?: string;
+ description?: string;
+}) {
+ const [value, setValue] = useState
(defaultValue);
+ return (
+
+
+
+
+
+
+
+ {description &&
{description}
}
+
+ );
+}
+
+export function ScopeBadge({ scope }: { scope?: Scope }) {
+ if (scope === "personal") {
+ return (
+
+
+ Bireysel
+
+ );
+ }
+ return null; // Company is default — no badge needed
+}
diff --git a/src/lib/appwrite/bank-account-actions.ts b/src/lib/appwrite/bank-account-actions.ts
index 8ec1cc4..f7e0ea3 100644
--- a/src/lib/appwrite/bank-account-actions.ts
+++ b/src/lib/appwrite/bank-account-actions.ts
@@ -1,11 +1,12 @@
"use server";
import { revalidatePath } from "next/cache";
-import { AppwriteException, ID, Permission, Query, Role } from "node-appwrite";
+import { AppwriteException, ID, Query } from "node-appwrite";
import { z } from "zod";
import { logAudit } from "./audit";
import { DATABASE_ID, TABLES, type BankAccount } from "./schema";
+import { canAccessRow, scopedRowPermissions } from "./scope-permissions";
import { createAdminClient } from "./server";
import { requireTenant } from "./tenant-guard";
import type { BankAccountActionState } from "./bank-account-types";
@@ -25,15 +26,6 @@ function flattenErrors(err: z.ZodError): Record {
return out;
}
-function teamRowPermissions(tenantId: string) {
- return [
- Permission.read(Role.team(tenantId)),
- Permission.update(Role.team(tenantId)),
- Permission.delete(Role.team(tenantId, "owner")),
- Permission.delete(Role.team(tenantId, "admin")),
- ];
-}
-
function pickFormFields(formData: FormData) {
return {
bankName: String(formData.get("bankName") ?? "").trim(),
@@ -41,6 +33,7 @@ function pickFormFields(formData: FormData) {
iban: String(formData.get("iban") ?? "").trim(),
openingBalance: String(formData.get("openingBalance") ?? "0"),
notes: String(formData.get("notes") ?? "").trim(),
+ scope: (formData.get("scope") as "company" | "personal" | null) ?? "company",
};
}
@@ -71,7 +64,7 @@ export async function createBankAccountAction(
createdBy: ctx.user.id,
...parsed.data,
},
- teamRowPermissions(ctx.tenantId),
+ scopedRowPermissions(ctx.tenantId, ctx.user.id, parsed.data.scope),
);
await logAudit({
@@ -116,11 +109,17 @@ export async function updateBankAccountAction(
TABLES.bankAccounts,
id,
)) as unknown as BankAccount;
- if (existing.tenantId !== ctx.tenantId) {
+ if (existing.tenantId !== ctx.tenantId || !canAccessRow(existing, ctx.user.id)) {
return { ok: false, error: "Erişim engellendi." };
}
- await tablesDB.updateRow(DATABASE_ID, TABLES.bankAccounts, id, parsed.data);
+ await tablesDB.updateRow(
+ DATABASE_ID,
+ TABLES.bankAccounts,
+ id,
+ parsed.data,
+ scopedRowPermissions(ctx.tenantId, existing.createdBy, parsed.data.scope),
+ );
await logAudit({
tenantId: ctx.tenantId,
@@ -158,7 +157,7 @@ export async function archiveBankAccountAction(
TABLES.bankAccounts,
id,
)) as unknown as BankAccount;
- if (existing.tenantId !== ctx.tenantId) {
+ if (existing.tenantId !== ctx.tenantId || !canAccessRow(existing, ctx.user.id)) {
return { ok: false, error: "Erişim engellendi." };
}
@@ -203,7 +202,7 @@ export async function deleteBankAccountAction(
TABLES.bankAccounts,
id,
)) as unknown as BankAccount;
- if (existing.tenantId !== ctx.tenantId) {
+ if (existing.tenantId !== ctx.tenantId || !canAccessRow(existing, ctx.user.id)) {
return { ok: false, error: "Erişim engellendi." };
}
diff --git a/src/lib/appwrite/bank-account-queries.ts b/src/lib/appwrite/bank-account-queries.ts
index 3c14e35..6175162 100644
--- a/src/lib/appwrite/bank-account-queries.ts
+++ b/src/lib/appwrite/bank-account-queries.ts
@@ -2,6 +2,7 @@ import "server-only";
import { Query } from "node-appwrite";
+import { canAccessRow } from "./scope-permissions";
import { createAdminClient } from "./server";
import {
DATABASE_ID,
@@ -10,7 +11,15 @@ import {
type FinanceEntry,
} from "./schema";
-export async function listBankAccounts(tenantId: string): Promise {
+/**
+ * Returns bank accounts the current user is allowed to see:
+ * - all `company` scope rows
+ * - personal-scope rows where createdBy === currentUserId
+ */
+export async function listBankAccounts(
+ tenantId: string,
+ currentUserId?: string,
+): Promise {
try {
const { tablesDB } = createAdminClient();
const result = await tablesDB.listRows({
@@ -22,30 +31,28 @@ export async function listBankAccounts(tenantId: string): Promise
Query.limit(200),
],
});
- return result.rows as unknown as BankAccount[];
+ const rows = result.rows as unknown as BankAccount[];
+ if (!currentUserId) return rows;
+ return rows.filter((r) => canAccessRow(r, currentUserId));
} catch {
return [];
}
}
/**
- * Computes a current balance for each account: openingBalance + Σ(income/receivable) − Σ(expense/debt).
+ * Computes a current balance for each visible account.
*/
export async function getBankAccountBalances(
tenantId: string,
+ currentUserId?: string,
): Promise