Files
lab/src/lib/appwrite/clinic-pricing-actions.ts
T
kovakmedya 48361792f0 perf(connections): collapse pricing N+1 into a single bulk query
The /connections page was firing one listClinicPricing call per approved
counterpart inside Promise.all. That meant a lab with 10 clinics paid 10
sequential Appwrite roundtrips on every load, and worse, every time the
ClinicPricingDialog saved a row revalidatePath('/connections') ran the
whole fan-out again — saving a single discount felt like the request had
hung.

Replaced the per-peer query with listAllPricingForLab /
listAllPricingForClinic (single Query.equal on the side-specific column,
limit 500) and group the result into a Map client-side. One roundtrip
regardless of how many connections you have.

Also flipped the audit-log calls in setClinicPricingAction /
clearClinicPricingAction from 'await logAudit(...)' to 'void logAudit(...)'
— audit is best-effort by design and never blocks the user-facing
mutation; awaiting it doubled the perceived latency for nothing.
2026-05-21 22:45:58 +03:00

201 lines
5.9 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.
"use server";
import { revalidatePath } from "next/cache";
import { AppwriteException, ID, Permission, Query, Role } from "node-appwrite";
import { z } from "zod";
import { logAudit } from "./audit";
import {
DATABASE_ID,
TABLES,
type ClinicPricing,
type Connection,
} from "./schema";
import { createAdminClient } from "./server";
import {
requireRole,
requireTenant,
requireTenantKind,
} from "./tenant-guard";
import type {
ClinicPricingActionState,
ClinicPricingFormState,
} from "./clinic-pricing-types";
import { clinicPricingSchema } from "@/lib/validation/clinic-pricing";
function appwriteError(e: unknown, fallback = "Beklenmeyen bir hata oluştu."): string {
if (e instanceof AppwriteException) return e.message || fallback;
return process.env.NODE_ENV !== "production" && e instanceof Error
? `${fallback} (${e.message})`
: fallback;
}
function flattenErrors(err: z.ZodError): Record<string, string> {
const out: Record<string, string> = {};
for (const issue of err.issues) {
const key = issue.path.join(".");
if (key && !out[key]) out[key] = issue.message;
}
return out;
}
function pricingPermissions(labTenantId: string, clinicTenantId: string): string[] {
return [
Permission.read(Role.team(labTenantId)),
Permission.read(Role.team(clinicTenantId)),
Permission.update(Role.team(labTenantId, "owner")),
Permission.update(Role.team(labTenantId, "admin")),
Permission.delete(Role.team(labTenantId, "owner")),
Permission.delete(Role.team(labTenantId, "admin")),
];
}
function pickFields(formData: FormData) {
return {
clinicTenantId: String(formData.get("clinicTenantId") ?? "").trim(),
prostheticType: String(formData.get("prostheticType") ?? "").trim(),
customUnitPrice: String(formData.get("customUnitPrice") ?? "").trim(),
discountPercent: String(formData.get("discountPercent") ?? "").trim(),
currency: String(formData.get("currency") ?? "").trim(),
};
}
export async function setClinicPricingAction(
_prev: ClinicPricingFormState,
formData: FormData,
): Promise<ClinicPricingFormState> {
let ctx;
try {
ctx = await requireTenant();
requireRole(ctx, ["owner", "admin"]);
requireTenantKind(ctx, ["lab"]);
} catch {
return { ok: false, error: "Fiyatlandırma yalnızca laboratuvar hesapları için." };
}
const parsed = clinicPricingSchema.safeParse(pickFields(formData));
if (!parsed.success) {
return { ok: false, error: "Form geçersiz.", fieldErrors: flattenErrors(parsed.error) };
}
const { tablesDB } = createAdminClient();
// Verify the lab actually has an approved connection with this clinic
const connRes = await tablesDB.listRows({
databaseId: DATABASE_ID,
tableId: TABLES.connections,
queries: [
Query.equal("labTenantId", ctx.tenantId),
Query.equal("clinicTenantId", parsed.data.clinicTenantId),
Query.equal("status", "approved"),
Query.limit(1),
],
});
if (!(connRes.rows[0] as unknown as Connection | undefined)) {
return {
ok: false,
error: "Onaylı bir bağlantınız yok.",
fieldErrors: { clinicTenantId: "Bağlantı yok." },
};
}
try {
const existing = await tablesDB.listRows({
databaseId: DATABASE_ID,
tableId: TABLES.clinicPricing,
queries: [
Query.equal("labTenantId", ctx.tenantId),
Query.equal("clinicTenantId", parsed.data.clinicTenantId),
Query.equal("prostheticType", parsed.data.prostheticType),
Query.limit(1),
],
});
const row = existing.rows[0] as unknown as ClinicPricing | undefined;
const payload = {
labTenantId: ctx.tenantId,
clinicTenantId: parsed.data.clinicTenantId,
prostheticType: parsed.data.prostheticType,
customUnitPrice: parsed.data.customUnitPrice,
discountPercent: parsed.data.discountPercent,
currency: parsed.data.currency,
createdBy: ctx.user.id,
};
if (row) {
await tablesDB.updateRow(DATABASE_ID, TABLES.clinicPricing, row.$id, payload);
void logAudit({
tenantId: ctx.tenantId,
userId: ctx.user.id,
action: "update",
entityType: "clinic_pricing",
entityId: row.$id,
changes: payload,
});
} else {
const created = await tablesDB.createRow(
DATABASE_ID,
TABLES.clinicPricing,
ID.unique(),
payload,
pricingPermissions(ctx.tenantId, parsed.data.clinicTenantId),
);
void logAudit({
tenantId: ctx.tenantId,
userId: ctx.user.id,
action: "create",
entityType: "clinic_pricing",
entityId: created.$id,
changes: payload,
});
}
} catch (e) {
return { ok: false, error: appwriteError(e, "Fiyatlandırma kaydedilemedi.") };
}
revalidatePath("/connections");
return { ok: true };
}
export async function clearClinicPricingAction(
_prev: ClinicPricingActionState,
formData: FormData,
): Promise<ClinicPricingActionState> {
const id = String(formData.get("id") ?? "").trim();
if (!id) return { ok: false, error: "Kayıt bulunamadı." };
let ctx;
try {
ctx = await requireTenant();
requireRole(ctx, ["owner", "admin"]);
requireTenantKind(ctx, ["lab"]);
} catch {
return { ok: false, error: "Yetkiniz yok." };
}
try {
const { tablesDB } = createAdminClient();
const row = (await tablesDB.getRow(
DATABASE_ID,
TABLES.clinicPricing,
id,
)) as unknown as ClinicPricing;
if (row.labTenantId !== ctx.tenantId) {
return { ok: false, error: "Yetkiniz yok." };
}
await tablesDB.deleteRow(DATABASE_ID, TABLES.clinicPricing, id);
void logAudit({
tenantId: ctx.tenantId,
userId: ctx.user.id,
action: "delete",
entityType: "clinic_pricing",
entityId: id,
});
} catch (e) {
return { ok: false, error: appwriteError(e, "Silinemedi.") };
}
revalidatePath("/connections");
return { ok: true };
}