diff --git a/src/routes/resources/contacts.ts b/src/routes/resources/contacts.ts new file mode 100644 index 0000000..ae5a675 --- /dev/null +++ b/src/routes/resources/contacts.ts @@ -0,0 +1,276 @@ +import { FastifyInstance } from "fastify" +import { + eq, + ilike, + asc, + desc, + and, + count, + inArray, + or +} from "drizzle-orm" + +import { + contacts, + customers, + vendors +} from "../../../db/schema" + + +// ------------------------------------------------------------- +// 🔍 Helper für SQL-Suche über mehrere Spalten +// ------------------------------------------------------------- +function buildSearchCondition(table: any, columns: string[], search: string) { + if (!search || !columns.length) return null + + const term = `%${search.toLowerCase()}%` + + const conditions = columns + .map(c => table[c]) + .filter(Boolean) + .map(col => ilike(col, term)) + + if (conditions.length === 0) return null + + // @ts-ignore + return or(...conditions) +} + + + +export default async function contactsRoutes(server: FastifyInstance) { + + + // ------------------------------------------------------------- + // LIST + // ------------------------------------------------------------- + server.get("/resource/contacts", 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 + } + + // Grundfilter + let whereCond: any = eq(contacts.tenant, tenantId) + + // 🔍 Suche + if (search) { + const searchCond = buildSearchCondition( + contacts, + ["firstName", "lastName", "email", "phone", "notes"], + search + ) + if (searchCond) whereCond = and(whereCond, searchCond) + } + + // Query + let q = server.db.select().from(contacts).where(whereCond) + + // Sortierung + if (sort) { + const field = (contacts as any)[sort] + if (field) { + //@ts-ignore + q = q.orderBy( + ascQuery === "true" ? asc(field) : desc(field) + ) + } + } + + return await q + }) + + + + // ------------------------------------------------------------- + // PAGINATED + // ------------------------------------------------------------- + server.get("/resource/contacts/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 CONDITIONS + // ----------------------------------- + let whereCond: any = eq(contacts.tenant, tenantId) + + // Filter + if (filters) { + for (const [key, val] of Object.entries(filters)) { + const col = (contacts 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)) + } + } + } + + // 🔍 Suche + if (search && search.trim().length > 0) { + const searchCond = buildSearchCondition( + contacts, + ["firstName", "lastName", "email", "phone", "notes"], + search + ) + if (searchCond) whereCond = and(whereCond, searchCond) + } + + // ----------------------------------- + // COUNT + // ----------------------------------- + const totalRes = await server.db + .select({ value: count(contacts.id) }) + .from(contacts) + .where(whereCond) + + const total = Number(totalRes[0]?.value ?? 0) + + // ----------------------------------- + // DISTINCT VALUES + // ----------------------------------- + const distinctValues: Record = {} + + if (distinctColumns) { + for (const colName of distinctColumns.split(",").map(v => v.trim())) { + const col = (contacts as any)[colName] + if (!col) continue + + const rows = await server.db + .select({ v: col }) + .from(contacts) + .where(eq(contacts.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 + + // ----------------------------------- + // ORDER + // ----------------------------------- + let orderField = null + let orderDirection: "asc" | "desc" = "asc" + + if (sort?.length > 0) { + const s = sort[0] + const col = (contacts as any)[s.field] + if (col) { + orderField = col + orderDirection = s.direction === "asc" ? "asc" : "desc" + } + } + + // ----------------------------------- + // DATA QUERY + // ----------------------------------- + let dataQuery = server.db + .select() + .from(contacts) + .where(whereCond) + .offset(offset) + .limit(limit) + + if (orderField) { + //@ts-ignore + dataQuery = + orderDirection === "asc" + ? dataQuery.orderBy(asc(orderField)) + : dataQuery.orderBy(desc(orderField)) + } + + const data = await dataQuery + + return { + data, + queryConfig: { + ...queryConfig, + total, + totalPages: pagination?.limit + ? Math.ceil(total / pagination.limit) + : 1, + distinctValues, + search: search || null + } + } + + } catch (err) { + console.error(err) + return reply.code(500).send({ error: "Internal Server Error" }) + } + }) + + + + // ------------------------------------------------------------- + // DETAIL (customer + vendor) + // ------------------------------------------------------------- + server.get("/resource/contacts/: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(contacts) + .where( + and( + eq(contacts.id, Number(id)), + eq(contacts.tenant, tenantId) + ) + ) + .limit(1) + + if (!rows.length) + return reply.code(404).send({ error: "Not found" }) + + const contact = rows[0] + + const [customerRecord, vendorRecord] = await Promise.all([ + contact.customer + ? server.db.select().from(customers).where(eq(customers.id, contact.customer)) + : [], + contact.vendor + ? server.db.select().from(vendors).where(eq(vendors.id, contact.vendor)) + : [], + ]) + + return { + ...contact, + customer: customerRecord[0] ?? null, + vendor: vendorRecord[0] ?? null, + } + }) + +} diff --git a/src/routes/resources/contracts.ts b/src/routes/resources/contracts.ts new file mode 100644 index 0000000..c0e8d6c --- /dev/null +++ b/src/routes/resources/contracts.ts @@ -0,0 +1,244 @@ +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, + } + }) +} diff --git a/src/routes/resources/customers.ts b/src/routes/resources/customers.ts index 5888b55..773b94f 100644 --- a/src/routes/resources/customers.ts +++ b/src/routes/resources/customers.ts @@ -4,7 +4,10 @@ import { ilike, asc, desc, - and, count, inArray, + and, + count, + inArray, + or, } from "drizzle-orm" import { @@ -17,7 +20,29 @@ import { statementallocations, files, events, -} from "../../../db/schema" // dein zentraler index.ts Export +} from "../../../db/schema" + + +// ------------------------------------------------------------- +// 🔍 Helper für SQL-Suche über mehrere Spalten +// ------------------------------------------------------------- +function buildSearchCondition(table: any, columns: string[], search: string) { + if (!search || !columns.length) return null + + const term = `%${search.toLowerCase()}%` + + const conditions = columns + .map((colName) => table[colName]) + .filter(Boolean) + .map((col) => ilike(col, term)) + + if (conditions.length === 0) return null + + // @ts-ignore + return or(...conditions) +} + + export default async function customerRoutes(server: FastifyInstance) { @@ -36,38 +61,38 @@ export default async function customerRoutes(server: FastifyInstance) { } // Basisquery + let whereCond: any = eq(customers.tenant, tenantId) + + // 🔍 SQL-Suche + if (search) { + const searchCond = buildSearchCondition( + customers, + ["name", "customerNumber", "firstname", "lastname", "notes"], + search + ) + + if (searchCond) { + whereCond = and(whereCond, searchCond) + } + } + let baseQuery = server.db .select() .from(customers) - .where(eq(customers.tenant, tenantId)) - - // Suche - if (search) { - baseQuery = server.db - .select() - .from(customers) - .where( - and( - eq(customers.tenant, tenantId), - ilike(customers.name, `%${search}%`) - ) - ) - } + .where(whereCond) // Sortierung if (sort) { const field = (customers as any)[sort] if (field) { - // @ts-ignore + //@ts-ignore baseQuery = baseQuery.orderBy( ascQuery === "true" ? asc(field) : desc(field) ) } } - const customerList = await baseQuery - - return customerList + return await baseQuery }) @@ -91,22 +116,19 @@ export default async function customerRoutes(server: FastifyInstance) { } = queryConfig const { - select, search, - searchColumns, distinctColumns } = req.query as { - select?: string search?: string - searchColumns?: string distinctColumns?: string } // ---------------------------- - // WHERE CONDITIONS + // WHERE CONDITIONS (Basis) // ---------------------------- let whereCond: any = eq(customers.tenant, tenantId) + // Filters if (filters) { for (const [key, val] of Object.entries(filters)) { const col = (customers as any)[key] @@ -114,24 +136,29 @@ export default async function customerRoutes(server: FastifyInstance) { if (Array.isArray(val)) { whereCond = and(whereCond, inArray(col, val)) - } else if (val === true || val === false || val === null) { - whereCond = and(whereCond, eq(col, val)) } else { - whereCond = and(whereCond, eq(col, val)) + whereCond = and(whereCond, eq(col, val as any)) } } } + // ---------------------------- + // 🔍 SEARCH + // ---------------------------- if (search && search.trim().length > 0) { - const searchTerm = `%${search.trim().toLowerCase()}%` - whereCond = and( - whereCond, - ilike(customers.name, searchTerm) + const searchCond = buildSearchCondition( + customers, + ["name", "customerNumber", "firstname", "lastname", "notes"], + search.trim() ) + + if (searchCond) { + whereCond = and(whereCond, searchCond) + } } // ---------------------------- - // COUNT FIX (Drizzle-safe) + // COUNT // ---------------------------- const totalRes = await server.db .select({ value: count(customers.id) }) @@ -141,7 +168,7 @@ export default async function customerRoutes(server: FastifyInstance) { const total = Number(totalRes[0]?.value ?? 0) // ---------------------------- - // DISTINCT VALUES (optional) + // DISTINCT VALUES // ---------------------------- const distinctValues: Record = {} @@ -155,11 +182,9 @@ export default async function customerRoutes(server: FastifyInstance) { .from(customers) .where(eq(customers.tenant, tenantId)) - const values = rows - .map(r => r.v) - .filter(v => v != null && v !== "") - - distinctValues[colName] = [...new Set(values)].sort() + distinctValues[colName] = + [...new Set(rows.map(r => r.v).filter(v => v != null && v !== ""))] + .sort() } } @@ -200,6 +225,7 @@ export default async function customerRoutes(server: FastifyInstance) { .limit(limit) if (orderField) { + //@ts-ignore dataQuery = orderDirection === "asc" ? dataQuery.orderBy(asc(orderField)) @@ -209,7 +235,7 @@ export default async function customerRoutes(server: FastifyInstance) { const data = await dataQuery // ---------------------------- - // BUILD RETURN CONFIG + // CONFIG RESPONSE // ---------------------------- const totalPages = pagination?.limit ? Math.ceil(total / pagination.limit) @@ -231,7 +257,6 @@ export default async function customerRoutes(server: FastifyInstance) { catch (e) { console.log(e) } - }) @@ -269,28 +294,11 @@ export default async function customerRoutes(server: FastifyInstance) { customerFiles, customerEvents, ] = await Promise.all([ - // Projekte, die dem Kunden zugeordnet sind - server.db - .select() - .from(projects) - .where(eq(projects.customer, Number(id))), + server.db.select().from(projects).where(eq(projects.customer, Number(id))), + server.db.select().from(plants).where(eq(plants.customer, Number(id))), + server.db.select().from(contracts).where(eq(contracts.customer, Number(id))), + server.db.select().from(contacts).where(eq(contacts.customer, Number(id))), - server.db - .select() - .from(plants) - .where(eq(plants.customer, Number(id))), - - server.db - .select() - .from(contracts) - .where(eq(contracts.customer, Number(id))), - - server.db - .select() - .from(contacts) - .where(eq(contacts.customer, Number(id))), - - // createddocuments + inner join statementallocations server.db .select({ ...createddocuments, @@ -303,15 +311,8 @@ export default async function customerRoutes(server: FastifyInstance) { ) .where(eq(createddocuments.customer, Number(id))), - server.db - .select() - .from(files) - .where(eq(files.customer, Number(id))), - - server.db - .select() - .from(events) - .where(eq(events.customer, Number(id))), + server.db.select().from(files).where(eq(files.customer, Number(id))), + server.db.select().from(events).where(eq(events.customer, Number(id))), ]) return { diff --git a/src/routes/resources/vendors.ts b/src/routes/resources/vendors.ts index cbcac84..4408f53 100644 --- a/src/routes/resources/vendors.ts +++ b/src/routes/resources/vendors.ts @@ -6,7 +6,8 @@ import { desc, and, count, - inArray + inArray, + or } from "drizzle-orm" import { @@ -15,6 +16,22 @@ import { files, } from "../../../db/schema" +// ------------------------------------------------------------- +// SQL Volltext-Suche (über mehrere relevante Felder) +// ------------------------------------------------------------- +function buildVendorSearchTerm(search?: string) { + if (!search) return null + + const term = `%${search.toLowerCase()}%` + + return or( + ilike(vendors.name, term), + ilike(vendors.vendorNumber, term), + ilike(vendors.notes, term), + ilike(vendors.defaultPaymentMethod, term) + ) +} + export default async function vendorRoutes(server: FastifyInstance) { // ------------------------------------------------------------- @@ -31,36 +48,30 @@ export default async function vendorRoutes(server: FastifyInstance) { asc?: string } - let baseQuery = server.db + // WHERE + let whereCond: any = eq(vendors.tenant, tenantId) + + const searchCond = buildVendorSearchTerm(search) + if (searchCond) whereCond = and(whereCond, searchCond) + + // QUERY + let q = server.db .select() .from(vendors) - .where(eq(vendors.tenant, tenantId)) + .where(whereCond) - // 🔎 Suche - if (search) { - baseQuery = server.db - .select() - .from(vendors) - .where( - and( - eq(vendors.tenant, tenantId), - ilike(vendors.name, `%${search}%`) - ) - ) - } - - // 🔽 Sortierung + // SORT if (sort) { - const field = (vendors as any)[sort] - if (field) { - // @ts-ignore - baseQuery = baseQuery.orderBy( - ascQuery === "true" ? asc(field) : desc(field) - ) + const col = (vendors as any)[sort] + if (col) { + //@ts-ignore + q = ascQuery === "true" + ? q.orderBy(asc(col)) + : q.orderBy(desc(col)) } } - return await baseQuery + return await q } catch (err) { console.error("ERROR /resource/vendors:", err) return reply.code(500).send({ error: "Internal Server Error" }) @@ -74,35 +85,22 @@ export default async function vendorRoutes(server: FastifyInstance) { server.get("/resource/vendors/paginated", async (req, reply) => { try { const tenantId = req.user?.tenant_id - if (!tenantId) { - return reply.code(400).send({ error: "No tenant selected" }) - } + if (!tenantId) return reply.code(400).send({ error: "No tenant selected" }) const queryConfig = req.queryConfig - const { - pagination, - sort, - filters, - paginationDisabled - } = queryConfig + const { pagination, sort, filters, paginationDisabled } = queryConfig - const { - select, - search, - searchColumns, - distinctColumns - } = req.query as { - select?: string + const { search, distinctColumns } = req.query as { search?: string - searchColumns?: string distinctColumns?: string } - // ---------------------------- - // WHERE CONDITIONS - // ---------------------------- + // ------------------------------------ + // WHERE + // ------------------------------------ let whereCond: any = eq(vendors.tenant, tenantId) + // Filters if (filters) { for (const [key, val] of Object.entries(filters)) { const col = (vendors as any)[key] @@ -110,24 +108,19 @@ export default async function vendorRoutes(server: FastifyInstance) { if (Array.isArray(val)) { whereCond = and(whereCond, inArray(col, val)) - } else if (val === true || val === false || val === null) { - whereCond = and(whereCond, eq(col, val)) } else { - whereCond = and(whereCond, eq(col, val)) + whereCond = and(whereCond, eq(col, val as any)) } } } - if (search && search.trim().length > 0) { - whereCond = and( - whereCond, - ilike(vendors.name, `%${search.trim().toLowerCase()}%`) - ) - } + // SEARCH + const searchCond = buildVendorSearchTerm(search) + if (searchCond) whereCond = and(whereCond, searchCond) - // ---------------------------- + // ------------------------------------ // COUNT - // ---------------------------- + // ------------------------------------ const totalRes = await server.db .select({ value: count(vendors.id) }) .from(vendors) @@ -135,14 +128,14 @@ export default async function vendorRoutes(server: FastifyInstance) { const total = Number(totalRes[0]?.value ?? 0) - // ---------------------------- - // DISTINCT FILTER VALUES - // ---------------------------- + // ------------------------------------ + // DISTINCT VALUES + // ------------------------------------ const distinctValues: Record = {} if (distinctColumns) { - for (const colName of distinctColumns.split(",").map(v => v.trim())) { - const col = (vendors as any)[colName] + for (const field of distinctColumns.split(",")) { + const col = (vendors as any)[field.trim()] if (!col) continue const rows = await server.db @@ -152,42 +145,37 @@ export default async function vendorRoutes(server: FastifyInstance) { const values = rows .map(r => r.v) - .filter(v => v != null && v !== "") + .filter(v => v !== null && v !== "") - distinctValues[colName] = [...new Set(values)].sort() + distinctValues[field] = [...new Set(values)].sort() } } - // ---------------------------- + // ------------------------------------ // PAGINATION - // ---------------------------- - let offset = 0 - let limit = 999999 + // ------------------------------------ + const offset = pagination?.offset ?? 0 + const limit = pagination?.limit ?? 5000 - if (!paginationDisabled && pagination) { - offset = pagination.offset - limit = pagination.limit - } - - // ---------------------------- - // ORDER BY - // ---------------------------- - let orderField = null - let orderDirection: "asc" | "desc" = "asc" + // ------------------------------------ + // SORT + // ------------------------------------ + let orderField: any = null + let orderDir: "asc" | "desc" = "asc" if (sort?.length > 0) { const s = sort[0] const col = (vendors as any)[s.field] if (col) { orderField = col - orderDirection = s.direction === "asc" ? "asc" : "desc" + orderDir = s.direction === "asc" ? "asc" : "desc" } } - // ---------------------------- - // QUERY DATA - // ---------------------------- - let dataQuery = server.db + // ------------------------------------ + // DATA QUERY + // ------------------------------------ + let q = server.db .select() .from(vendors) .where(whereCond) @@ -195,27 +183,27 @@ export default async function vendorRoutes(server: FastifyInstance) { .limit(limit) if (orderField) { - dataQuery = - orderDirection === "asc" - ? dataQuery.orderBy(asc(orderField)) - : dataQuery.orderBy(desc(orderField)) + //@ts-ignore + q = orderDir === "asc" + ? q.orderBy(asc(orderField)) + : q.orderBy(desc(orderField)) } - const data = await dataQuery + const data = await q - const totalPages = pagination?.limit - ? Math.ceil(total / pagination.limit) - : 1 - - const enrichedConfig = { - ...queryConfig, - total, - totalPages, - distinctValues, - search: search || null, + return { + data, + queryConfig: { + ...queryConfig, + total, + totalPages: pagination?.limit + ? Math.ceil(total / pagination.limit) + : 1, + distinctValues, + search: search || null + } } - return { data, queryConfig: enrichedConfig } } catch (err) { console.error("ERROR /resource/vendors/paginated:", err) return reply.code(500).send({ error: "Internal Server Error" }) @@ -223,59 +211,38 @@ export default async function vendorRoutes(server: FastifyInstance) { }) - // ------------------------------------------------------------- - // DETAIL (mit JOINS) + // DETAIL (mit JOINs: contacts, files) // ------------------------------------------------------------- server.get("/resource/vendors/:id", async (req, reply) => { try { 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 vendorId = Number(id) - const vendorRecord = await server.db + const vendorRows = await server.db .select() .from(vendors) - .where( - and( - eq(vendors.id, vendorId), - eq(vendors.tenant, tenantId) - ) - ) + .where(and(eq(vendors.id, vendorId), eq(vendors.tenant, tenantId))) .limit(1) - if (!vendorRecord.length) { - return reply.code(404).send({ error: "Vendor not found" }) - } + if (!vendorRows.length) return reply.code(404).send({ error: "Vendor not found" }) - const vendor = vendorRecord[0] + const vendor = vendorRows[0] - // ---------------------------- - // RELATIONS - // ---------------------------- - const [ - vendorContacts, - vendorFiles, - ] = await Promise.all([ - server.db - .select() - .from(contacts) - .where(eq(contacts.vendor, vendorId)), - - server.db - .select() - .from(files) - .where(eq(files.vendor, vendorId)), + const [vendorContacts, vendorFiles] = await Promise.all([ + server.db.select().from(contacts).where(eq(contacts.vendor, vendorId)), + server.db.select().from(files).where(eq(files.vendor, vendorId)) ]) return { ...vendor, contacts: vendorContacts, - files: vendorFiles, + files: vendorFiles } + } catch (err) { console.error("ERROR /resource/vendors/:id:", err) return reply.code(500).send({ error: "Internal Server Error" })