Redone Routes customers,contracts,contacts,vendors

This commit is contained in:
2025-12-06 20:33:27 +01:00
parent 765f3c42c1
commit dff2b05401
4 changed files with 687 additions and 199 deletions

View File

@@ -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<string, any[]> = {}
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,
}
})
}

View File

@@ -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: SQLLIKE 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<string, any[]> = {}
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,
}
})
}

View File

@@ -4,7 +4,10 @@ import {
ilike, ilike,
asc, asc,
desc, desc,
and, count, inArray, and,
count,
inArray,
or,
} from "drizzle-orm" } from "drizzle-orm"
import { import {
@@ -17,7 +20,29 @@ import {
statementallocations, statementallocations,
files, files,
events, 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) { export default async function customerRoutes(server: FastifyInstance) {
@@ -36,38 +61,38 @@ export default async function customerRoutes(server: FastifyInstance) {
} }
// Basisquery // 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 let baseQuery = server.db
.select() .select()
.from(customers) .from(customers)
.where(eq(customers.tenant, tenantId)) .where(whereCond)
// Suche
if (search) {
baseQuery = server.db
.select()
.from(customers)
.where(
and(
eq(customers.tenant, tenantId),
ilike(customers.name, `%${search}%`)
)
)
}
// Sortierung // Sortierung
if (sort) { if (sort) {
const field = (customers as any)[sort] const field = (customers as any)[sort]
if (field) { if (field) {
// @ts-ignore //@ts-ignore
baseQuery = baseQuery.orderBy( baseQuery = baseQuery.orderBy(
ascQuery === "true" ? asc(field) : desc(field) ascQuery === "true" ? asc(field) : desc(field)
) )
} }
} }
const customerList = await baseQuery return await baseQuery
return customerList
}) })
@@ -91,22 +116,19 @@ export default async function customerRoutes(server: FastifyInstance) {
} = queryConfig } = queryConfig
const { const {
select,
search, search,
searchColumns,
distinctColumns distinctColumns
} = req.query as { } = req.query as {
select?: string
search?: string search?: string
searchColumns?: string
distinctColumns?: string distinctColumns?: string
} }
// ---------------------------- // ----------------------------
// WHERE CONDITIONS // WHERE CONDITIONS (Basis)
// ---------------------------- // ----------------------------
let whereCond: any = eq(customers.tenant, tenantId) let whereCond: any = eq(customers.tenant, tenantId)
// Filters
if (filters) { if (filters) {
for (const [key, val] of Object.entries(filters)) { for (const [key, val] of Object.entries(filters)) {
const col = (customers as any)[key] const col = (customers as any)[key]
@@ -114,24 +136,29 @@ export default async function customerRoutes(server: FastifyInstance) {
if (Array.isArray(val)) { if (Array.isArray(val)) {
whereCond = and(whereCond, inArray(col, val)) whereCond = and(whereCond, inArray(col, val))
} else if (val === true || val === false || val === null) {
whereCond = and(whereCond, eq(col, val))
} else { } else {
whereCond = and(whereCond, eq(col, val)) whereCond = and(whereCond, eq(col, val as any))
} }
} }
} }
if (search && search.trim().length > 0) {
const searchTerm = `%${search.trim().toLowerCase()}%`
whereCond = and(
whereCond,
ilike(customers.name, searchTerm)
)
}
// ---------------------------- // ----------------------------
// COUNT FIX (Drizzle-safe) // 🔍 SEARCH
// ----------------------------
if (search && search.trim().length > 0) {
const searchCond = buildSearchCondition(
customers,
["name", "customerNumber", "firstname", "lastname", "notes"],
search.trim()
)
if (searchCond) {
whereCond = and(whereCond, searchCond)
}
}
// ----------------------------
// COUNT
// ---------------------------- // ----------------------------
const totalRes = await server.db const totalRes = await server.db
.select({ value: count(customers.id) }) .select({ value: count(customers.id) })
@@ -141,7 +168,7 @@ export default async function customerRoutes(server: FastifyInstance) {
const total = Number(totalRes[0]?.value ?? 0) const total = Number(totalRes[0]?.value ?? 0)
// ---------------------------- // ----------------------------
// DISTINCT VALUES (optional) // DISTINCT VALUES
// ---------------------------- // ----------------------------
const distinctValues: Record<string, any[]> = {} const distinctValues: Record<string, any[]> = {}
@@ -155,11 +182,9 @@ export default async function customerRoutes(server: FastifyInstance) {
.from(customers) .from(customers)
.where(eq(customers.tenant, tenantId)) .where(eq(customers.tenant, tenantId))
const values = rows distinctValues[colName] =
.map(r => r.v) [...new Set(rows.map(r => r.v).filter(v => v != null && v !== ""))]
.filter(v => v != null && v !== "") .sort()
distinctValues[colName] = [...new Set(values)].sort()
} }
} }
@@ -200,6 +225,7 @@ export default async function customerRoutes(server: FastifyInstance) {
.limit(limit) .limit(limit)
if (orderField) { if (orderField) {
//@ts-ignore
dataQuery = dataQuery =
orderDirection === "asc" orderDirection === "asc"
? dataQuery.orderBy(asc(orderField)) ? dataQuery.orderBy(asc(orderField))
@@ -209,7 +235,7 @@ export default async function customerRoutes(server: FastifyInstance) {
const data = await dataQuery const data = await dataQuery
// ---------------------------- // ----------------------------
// BUILD RETURN CONFIG // CONFIG RESPONSE
// ---------------------------- // ----------------------------
const totalPages = pagination?.limit const totalPages = pagination?.limit
? Math.ceil(total / pagination.limit) ? Math.ceil(total / pagination.limit)
@@ -231,7 +257,6 @@ export default async function customerRoutes(server: FastifyInstance) {
catch (e) { catch (e) {
console.log(e) console.log(e)
} }
}) })
@@ -269,28 +294,11 @@ export default async function customerRoutes(server: FastifyInstance) {
customerFiles, customerFiles,
customerEvents, customerEvents,
] = await Promise.all([ ] = await Promise.all([
// Projekte, die dem Kunden zugeordnet sind server.db.select().from(projects).where(eq(projects.customer, Number(id))),
server.db server.db.select().from(plants).where(eq(plants.customer, Number(id))),
.select() server.db.select().from(contracts).where(eq(contracts.customer, Number(id))),
.from(projects) server.db.select().from(contacts).where(eq(contacts.customer, Number(id))),
.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))),
// createddocuments + inner join statementallocations
server.db server.db
.select({ .select({
...createddocuments, ...createddocuments,
@@ -303,15 +311,8 @@ export default async function customerRoutes(server: FastifyInstance) {
) )
.where(eq(createddocuments.customer, Number(id))), .where(eq(createddocuments.customer, Number(id))),
server.db server.db.select().from(files).where(eq(files.customer, Number(id))),
.select() server.db.select().from(events).where(eq(events.customer, Number(id))),
.from(files)
.where(eq(files.customer, Number(id))),
server.db
.select()
.from(events)
.where(eq(events.customer, Number(id))),
]) ])
return { return {

View File

@@ -6,7 +6,8 @@ import {
desc, desc,
and, and,
count, count,
inArray inArray,
or
} from "drizzle-orm" } from "drizzle-orm"
import { import {
@@ -15,6 +16,22 @@ import {
files, files,
} from "../../../db/schema" } 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) { export default async function vendorRoutes(server: FastifyInstance) {
// ------------------------------------------------------------- // -------------------------------------------------------------
@@ -31,36 +48,30 @@ export default async function vendorRoutes(server: FastifyInstance) {
asc?: string 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() .select()
.from(vendors) .from(vendors)
.where(eq(vendors.tenant, tenantId)) .where(whereCond)
// 🔎 Suche // SORT
if (search) {
baseQuery = server.db
.select()
.from(vendors)
.where(
and(
eq(vendors.tenant, tenantId),
ilike(vendors.name, `%${search}%`)
)
)
}
// 🔽 Sortierung
if (sort) { if (sort) {
const field = (vendors as any)[sort] const col = (vendors as any)[sort]
if (field) { if (col) {
// @ts-ignore //@ts-ignore
baseQuery = baseQuery.orderBy( q = ascQuery === "true"
ascQuery === "true" ? asc(field) : desc(field) ? q.orderBy(asc(col))
) : q.orderBy(desc(col))
} }
} }
return await baseQuery return await q
} catch (err) { } catch (err) {
console.error("ERROR /resource/vendors:", err) console.error("ERROR /resource/vendors:", err)
return reply.code(500).send({ error: "Internal Server Error" }) 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) => { server.get("/resource/vendors/paginated", async (req, reply) => {
try { try {
const tenantId = req.user?.tenant_id const tenantId = req.user?.tenant_id
if (!tenantId) { if (!tenantId) return reply.code(400).send({ error: "No tenant selected" })
return reply.code(400).send({ error: "No tenant selected" })
}
const queryConfig = req.queryConfig const queryConfig = req.queryConfig
const { const { pagination, sort, filters, paginationDisabled } = queryConfig
pagination,
sort,
filters,
paginationDisabled
} = queryConfig
const { const { search, distinctColumns } = req.query as {
select,
search,
searchColumns,
distinctColumns
} = req.query as {
select?: string
search?: string search?: string
searchColumns?: string
distinctColumns?: string distinctColumns?: string
} }
// ---------------------------- // ------------------------------------
// WHERE CONDITIONS // WHERE
// ---------------------------- // ------------------------------------
let whereCond: any = eq(vendors.tenant, tenantId) let whereCond: any = eq(vendors.tenant, tenantId)
// Filters
if (filters) { if (filters) {
for (const [key, val] of Object.entries(filters)) { for (const [key, val] of Object.entries(filters)) {
const col = (vendors as any)[key] const col = (vendors as any)[key]
@@ -110,24 +108,19 @@ export default async function vendorRoutes(server: FastifyInstance) {
if (Array.isArray(val)) { if (Array.isArray(val)) {
whereCond = and(whereCond, inArray(col, val)) whereCond = and(whereCond, inArray(col, val))
} else if (val === true || val === false || val === null) {
whereCond = and(whereCond, eq(col, val))
} else { } else {
whereCond = and(whereCond, eq(col, val)) whereCond = and(whereCond, eq(col, val as any))
} }
} }
} }
if (search && search.trim().length > 0) { // SEARCH
whereCond = and( const searchCond = buildVendorSearchTerm(search)
whereCond, if (searchCond) whereCond = and(whereCond, searchCond)
ilike(vendors.name, `%${search.trim().toLowerCase()}%`)
)
}
// ---------------------------- // ------------------------------------
// COUNT // COUNT
// ---------------------------- // ------------------------------------
const totalRes = await server.db const totalRes = await server.db
.select({ value: count(vendors.id) }) .select({ value: count(vendors.id) })
.from(vendors) .from(vendors)
@@ -135,14 +128,14 @@ export default async function vendorRoutes(server: FastifyInstance) {
const total = Number(totalRes[0]?.value ?? 0) const total = Number(totalRes[0]?.value ?? 0)
// ---------------------------- // ------------------------------------
// DISTINCT FILTER VALUES // DISTINCT VALUES
// ---------------------------- // ------------------------------------
const distinctValues: Record<string, any[]> = {} const distinctValues: Record<string, any[]> = {}
if (distinctColumns) { if (distinctColumns) {
for (const colName of distinctColumns.split(",").map(v => v.trim())) { for (const field of distinctColumns.split(",")) {
const col = (vendors as any)[colName] const col = (vendors as any)[field.trim()]
if (!col) continue if (!col) continue
const rows = await server.db const rows = await server.db
@@ -152,42 +145,37 @@ export default async function vendorRoutes(server: FastifyInstance) {
const values = rows const values = rows
.map(r => r.v) .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 // PAGINATION
// ---------------------------- // ------------------------------------
let offset = 0 const offset = pagination?.offset ?? 0
let limit = 999999 const limit = pagination?.limit ?? 5000
if (!paginationDisabled && pagination) { // ------------------------------------
offset = pagination.offset // SORT
limit = pagination.limit // ------------------------------------
} let orderField: any = null
let orderDir: "asc" | "desc" = "asc"
// ----------------------------
// ORDER BY
// ----------------------------
let orderField = null
let orderDirection: "asc" | "desc" = "asc"
if (sort?.length > 0) { if (sort?.length > 0) {
const s = sort[0] const s = sort[0]
const col = (vendors as any)[s.field] const col = (vendors as any)[s.field]
if (col) { if (col) {
orderField = col orderField = col
orderDirection = s.direction === "asc" ? "asc" : "desc" orderDir = s.direction === "asc" ? "asc" : "desc"
} }
} }
// ---------------------------- // ------------------------------------
// QUERY DATA // DATA QUERY
// ---------------------------- // ------------------------------------
let dataQuery = server.db let q = server.db
.select() .select()
.from(vendors) .from(vendors)
.where(whereCond) .where(whereCond)
@@ -195,27 +183,27 @@ export default async function vendorRoutes(server: FastifyInstance) {
.limit(limit) .limit(limit)
if (orderField) { if (orderField) {
dataQuery = //@ts-ignore
orderDirection === "asc" q = orderDir === "asc"
? dataQuery.orderBy(asc(orderField)) ? q.orderBy(asc(orderField))
: dataQuery.orderBy(desc(orderField)) : q.orderBy(desc(orderField))
} }
const data = await dataQuery const data = await q
const totalPages = pagination?.limit return {
? Math.ceil(total / pagination.limit) data,
: 1 queryConfig: {
const enrichedConfig = {
...queryConfig, ...queryConfig,
total, total,
totalPages, totalPages: pagination?.limit
? Math.ceil(total / pagination.limit)
: 1,
distinctValues, distinctValues,
search: search || null, search: search || null
}
} }
return { data, queryConfig: enrichedConfig }
} catch (err) { } catch (err) {
console.error("ERROR /resource/vendors/paginated:", err) console.error("ERROR /resource/vendors/paginated:", err)
return reply.code(500).send({ error: "Internal Server Error" }) 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) => { server.get("/resource/vendors/:id", async (req, reply) => {
try { try {
const { id } = req.params as { id: string } const { id } = req.params as { id: string }
const tenantId = req.user?.tenant_id 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 vendorId = Number(id) const vendorId = Number(id)
const vendorRecord = await server.db const vendorRows = await server.db
.select() .select()
.from(vendors) .from(vendors)
.where( .where(and(eq(vendors.id, vendorId), eq(vendors.tenant, tenantId)))
and(
eq(vendors.id, vendorId),
eq(vendors.tenant, tenantId)
)
)
.limit(1) .limit(1)
if (!vendorRecord.length) { if (!vendorRows.length) return reply.code(404).send({ error: "Vendor not found" })
return reply.code(404).send({ error: "Vendor not found" })
}
const vendor = vendorRecord[0] const vendor = vendorRows[0]
// ---------------------------- const [vendorContacts, vendorFiles] = await Promise.all([
// RELATIONS 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 { return {
...vendor, ...vendor,
contacts: vendorContacts, contacts: vendorContacts,
files: vendorFiles, files: vendorFiles
} }
} catch (err) { } catch (err) {
console.error("ERROR /resource/vendors/:id:", err) console.error("ERROR /resource/vendors/:id:", err)
return reply.code(500).send({ error: "Internal Server Error" }) return reply.code(500).send({ error: "Internal Server Error" })