import { FastifyInstance } from "fastify" import { eq, ilike, asc, desc, and, count, inArray, or } from "drizzle-orm" import { contracts, customers, files } from "../../../db/schema" // ------------------------------------------------------------- // Helper: SQL‐LIKE Suche über mehrere Felder // ------------------------------------------------------------- function buildSearchCondition(table: any, columns: string[], search?: string) { if (!search) return null const term = `%${search.toLowerCase()}%` const conditions = columns .map(col => table[col]) .filter(Boolean) .map(col => ilike(col, term)) if (conditions.length === 0) return null // @ts-ignore return or(...conditions) } export default async function contractsRoutes(server: FastifyInstance) { // ------------------------------------------------------------- // LIST // ------------------------------------------------------------- server.get("/resource/contracts", async (req, reply) => { const tenantId = req.user?.tenant_id if (!tenantId) return reply.code(400).send({ error: "No tenant selected" }) const { search, sort, asc: ascQuery } = req.query as { search?: string sort?: string asc?: string } let whereCond: any = eq(contracts.tenant, tenantId) // SQL SEARCH const searchCond = buildSearchCondition( contracts, ["name", "notes", "contractNumber", "paymentType", "sepaRef", "bankingName"], search ) if (searchCond) whereCond = and(whereCond, searchCond) // Query let q = server.db .select() .from(contracts) .where(whereCond) // SORT if (sort) { const field = (contracts as any)[sort] if (field) { //@ts-ignore q = q.orderBy( ascQuery === "true" ? asc(field) : desc(field) ) } } return await q }) // ------------------------------------------------------------- // PAGINATED // ------------------------------------------------------------- server.get("/resource/contracts/paginated", async (req, reply) => { try { const tenantId = req.user?.tenant_id if (!tenantId) return reply.code(400).send({ error: "No tenant selected" }) const queryConfig = req.queryConfig const { pagination, sort, filters, paginationDisabled } = queryConfig const { search, distinctColumns } = req.query as { search?: string distinctColumns?: string } // ----------------------------------- // WHERE // ----------------------------------- let whereCond: any = eq(contracts.tenant, tenantId) if (filters) { for (const [key, val] of Object.entries(filters)) { const col = (contracts as any)[key] if (!col) continue if (Array.isArray(val)) { whereCond = and(whereCond, inArray(col, val)) } else { whereCond = and(whereCond, eq(col, val as any)) } } } // SQL SEARCH const searchCond = buildSearchCondition( contracts, ["name", "notes", "contractNumber", "paymentType", "sepaRef", "bankingName"], search ) if (searchCond) whereCond = and(whereCond, searchCond) // ----------------------------------- // COUNT // ----------------------------------- const totalRes = await server.db .select({ value: count(contracts.id) }) .from(contracts) .where(whereCond) const total = Number(totalRes[0]?.value ?? 0) // ----------------------------------- // DISTINCT // ----------------------------------- const distinctValues: Record = {} if (distinctColumns) { for (const colName of distinctColumns.split(",")) { const col = (contracts as any)[colName.trim()] if (!col) continue const rows = await server.db .select({ v: col }) .from(contracts) .where(eq(contracts.tenant, tenantId)) distinctValues[colName] = [...new Set(rows.map(r => r.v).filter(v => v != null && v !== ""))] .sort() } } // ----------------------------------- // PAGINATION // ----------------------------------- let offset = pagination?.offset ?? 0 let limit = pagination?.limit ?? 999999 // ----------------------------------- // SORT // ----------------------------------- let orderField = null let orderDir: "asc" | "desc" = "asc" if (sort?.length > 0) { const s = sort[0] const col = (contracts as any)[s.field] if (col) { orderField = col orderDir = s.direction === "asc" ? "asc" : "desc" } } // ----------------------------------- // QUERY DATA // ----------------------------------- let q = server.db .select() .from(contracts) .where(whereCond) .offset(offset) .limit(limit) if (orderField) { //@ts-ignore q = orderDir === "asc" ? q.orderBy(asc(orderField)) : q.orderBy(desc(orderField)) } const data = await q return { data, queryConfig: { ...queryConfig, total, totalPages: pagination?.limit ? Math.ceil(total / pagination.limit) : 1, distinctValues, search: search || null, } } } catch (e) { console.error(e) return reply.code(500).send({ error: "Internal Server Error" }) } }) // ------------------------------------------------------------- // DETAIL (+ JOINS) // ------------------------------------------------------------- server.get("/resource/contracts/:id", async (req, reply) => { const { id } = req.params as { id: string } const tenantId = req.user?.tenant_id if (!tenantId) return reply.code(400).send({ error: "No tenant selected" }) const rows = await server.db .select() .from(contracts) .where(and(eq(contracts.id, Number(id)), eq(contracts.tenant, tenantId))) .limit(1) if (!rows.length) return reply.code(404).send({ error: "Not found" }) const contract = rows[0] const [customerRecord, fileList] = await Promise.all([ contract.customer ? server.db.select().from(customers).where(eq(customers.id, contract.customer)) : [], server.db.select().from(files).where(eq(files.contract, Number(id))), ]) return { ...contract, customer: customerRecord[0] ?? null, files: fileList, } }) }