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.
This commit is contained in:
@@ -1,7 +1,10 @@
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { listClinicPricing } from "@/lib/appwrite/clinic-pricing-queries";
|
import {
|
||||||
|
listAllPricingForClinic,
|
||||||
|
listAllPricingForLab,
|
||||||
|
} from "@/lib/appwrite/clinic-pricing-queries";
|
||||||
import {
|
import {
|
||||||
listApprovedConnections,
|
listApprovedConnections,
|
||||||
listPendingInbound,
|
listPendingInbound,
|
||||||
@@ -37,29 +40,18 @@ export default async function ConnectionsPage() {
|
|||||||
listPendingOutbound(ctx.tenantId, ctx.user.id),
|
listPendingOutbound(ctx.tenantId, ctx.user.id),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Lab side: load pricing rules per approved clinic so the dialog can
|
// Single bulk query for pricing then group client-side. Avoids N+1 across
|
||||||
// surface existing rules. Clinic side leaves this empty — it's display-only
|
// approved counterparts (was the main reason the save dialog felt slow —
|
||||||
// for them (see ConnectionsTable).
|
// every revalidatePath of /connections fanned out one query per peer).
|
||||||
|
const allPricing = isLab
|
||||||
|
? await listAllPricingForLab(ctx.tenantId)
|
||||||
|
: await listAllPricingForClinic(ctx.tenantId);
|
||||||
const pricingByCounterpart = new Map<string, ClinicPricing[]>();
|
const pricingByCounterpart = new Map<string, ClinicPricing[]>();
|
||||||
if (isLab) {
|
for (const row of allPricing) {
|
||||||
const pricingLists = await Promise.all(
|
const key = isLab ? row.clinicTenantId : row.labTenantId;
|
||||||
approved.map((c) =>
|
const bucket = pricingByCounterpart.get(key);
|
||||||
listClinicPricing(ctx.tenantId, c.clinicTenantId).then(
|
if (bucket) bucket.push(row);
|
||||||
(rows) => [c.clinicTenantId, rows] as const,
|
else pricingByCounterpart.set(key, [row]);
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
for (const [id, rows] of pricingLists) pricingByCounterpart.set(id, rows);
|
|
||||||
} else {
|
|
||||||
// Clinic side: only its own pricing rules from each lab, read-only
|
|
||||||
const pricingLists = await Promise.all(
|
|
||||||
approved.map((c) =>
|
|
||||||
listClinicPricing(c.labTenantId, ctx.tenantId).then(
|
|
||||||
(rows) => [c.labTenantId, rows] as const,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
for (const [id, rows] of pricingLists) pricingByCounterpart.set(id, rows);
|
|
||||||
}
|
}
|
||||||
const pricingObject: Record<string, ClinicPricing[]> = Object.fromEntries(
|
const pricingObject: Record<string, ClinicPricing[]> = Object.fromEntries(
|
||||||
pricingByCounterpart,
|
pricingByCounterpart,
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ export async function setClinicPricingAction(
|
|||||||
|
|
||||||
if (row) {
|
if (row) {
|
||||||
await tablesDB.updateRow(DATABASE_ID, TABLES.clinicPricing, row.$id, payload);
|
await tablesDB.updateRow(DATABASE_ID, TABLES.clinicPricing, row.$id, payload);
|
||||||
await logAudit({
|
void logAudit({
|
||||||
tenantId: ctx.tenantId,
|
tenantId: ctx.tenantId,
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
action: "update",
|
action: "update",
|
||||||
@@ -140,7 +140,7 @@ export async function setClinicPricingAction(
|
|||||||
payload,
|
payload,
|
||||||
pricingPermissions(ctx.tenantId, parsed.data.clinicTenantId),
|
pricingPermissions(ctx.tenantId, parsed.data.clinicTenantId),
|
||||||
);
|
);
|
||||||
await logAudit({
|
void logAudit({
|
||||||
tenantId: ctx.tenantId,
|
tenantId: ctx.tenantId,
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
action: "create",
|
action: "create",
|
||||||
@@ -184,7 +184,7 @@ export async function clearClinicPricingAction(
|
|||||||
return { ok: false, error: "Yetkiniz yok." };
|
return { ok: false, error: "Yetkiniz yok." };
|
||||||
}
|
}
|
||||||
await tablesDB.deleteRow(DATABASE_ID, TABLES.clinicPricing, id);
|
await tablesDB.deleteRow(DATABASE_ID, TABLES.clinicPricing, id);
|
||||||
await logAudit({
|
void logAudit({
|
||||||
tenantId: ctx.tenantId,
|
tenantId: ctx.tenantId,
|
||||||
userId: ctx.user.id,
|
userId: ctx.user.id,
|
||||||
action: "delete",
|
action: "delete",
|
||||||
|
|||||||
@@ -29,3 +29,28 @@ export async function listClinicPricingForClinic(
|
|||||||
): Promise<ClinicPricing[]> {
|
): Promise<ClinicPricing[]> {
|
||||||
return listClinicPricing(labTenantId, clinicTenantId);
|
return listClinicPricing(labTenantId, clinicTenantId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Single bulk query so /connections doesn't N+1 across approved counterparts. */
|
||||||
|
export async function listAllPricingForLab(
|
||||||
|
labTenantId: string,
|
||||||
|
): Promise<ClinicPricing[]> {
|
||||||
|
const { tablesDB } = createAdminClient();
|
||||||
|
const result = await tablesDB.listRows({
|
||||||
|
databaseId: DATABASE_ID,
|
||||||
|
tableId: TABLES.clinicPricing,
|
||||||
|
queries: [Query.equal("labTenantId", labTenantId), Query.limit(500)],
|
||||||
|
});
|
||||||
|
return toPlain(result.rows as unknown as ClinicPricing[]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function listAllPricingForClinic(
|
||||||
|
clinicTenantId: string,
|
||||||
|
): Promise<ClinicPricing[]> {
|
||||||
|
const { tablesDB } = createAdminClient();
|
||||||
|
const result = await tablesDB.listRows({
|
||||||
|
databaseId: DATABASE_ID,
|
||||||
|
tableId: TABLES.clinicPricing,
|
||||||
|
queries: [Query.equal("clinicTenantId", clinicTenantId), Query.limit(500)],
|
||||||
|
});
|
||||||
|
return toPlain(result.rows as unknown as ClinicPricing[]);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user