import { and, asc, eq, inArray } from "drizzle-orm"; import type { EstimateBalanceSnapshot } from "../../shared/estimate-balance/estimate-balance.js"; import type { AppDatabase } from "../database-context.js"; import { estimateBalances, estimateGroups, estimateRows, } from "../schema/estimate-balances.js"; /** Reads complete balance documents of a project, ordered like the UI shows them. */ export function readEstimateBalances( database: AppDatabase, projectId: string, balanceId?: string ): EstimateBalanceSnapshot[] { const balances = database .select() .from(estimateBalances) .where( balanceId ? and( eq(estimateBalances.projectId, projectId), eq(estimateBalances.id, balanceId) ) : eq(estimateBalances.projectId, projectId) ) .orderBy(asc(estimateBalances.sortOrder), asc(estimateBalances.id)) .all(); if (!balances.length) { return []; } const groups = database .select() .from(estimateGroups) .where(inArray(estimateGroups.balanceId, balances.map((balance) => balance.id))) .orderBy(asc(estimateGroups.sortOrder), asc(estimateGroups.id)) .all(); const rows = groups.length ? database .select() .from(estimateRows) .where(inArray(estimateRows.groupId, groups.map((group) => group.id))) .orderBy(asc(estimateRows.sortOrder), asc(estimateRows.id)) .all() : []; return balances.map((balance) => ({ id: balance.id, projectId: balance.projectId, name: balance.name, workStage: balance.workStage, dateIso: balance.dateIso, reserveAvPercent: balance.reserveAvPercent, reserveSvPercent: balance.reserveSvPercent, sortOrder: balance.sortOrder, groups: groups .filter((group) => group.balanceId === balance.id) .map((group) => ({ id: group.id, costGroup: group.costGroup, title: group.title, editor: group.editor, sortOrder: group.sortOrder, rows: rows .filter((row) => row.groupId === group.id) .map(({ groupId: _groupId, ...row }) => row), })), })); } /** Writes a complete balance document (the caller removed an older version first). */ export function insertEstimateBalance( database: AppDatabase, balance: EstimateBalanceSnapshot ) { database .insert(estimateBalances) .values({ id: balance.id, projectId: balance.projectId, name: balance.name, workStage: balance.workStage, dateIso: balance.dateIso, reserveAvPercent: balance.reserveAvPercent, reserveSvPercent: balance.reserveSvPercent, sortOrder: balance.sortOrder, }) .run(); for (const group of balance.groups) { database .insert(estimateGroups) .values({ id: group.id, balanceId: balance.id, costGroup: group.costGroup, title: group.title, editor: group.editor, sortOrder: group.sortOrder, }) .run(); if (group.rows.length) { database .insert(estimateRows) .values(group.rows.map((row) => ({ ...row, groupId: group.id }))) .run(); } } } /** Removes a balance with its groups and rows (foreign keys cascade). */ export function deleteEstimateBalance( database: AppDatabase, projectId: string, balanceId: string ) { return database .delete(estimateBalances) .where( and(eq(estimateBalances.projectId, projectId), eq(estimateBalances.id, balanceId)) ) .run().changes; }