285 lines
8.7 KiB
TypeScript
285 lines
8.7 KiB
TypeScript
import { FastifyInstance } from "fastify"
|
|
import {
|
|
eq,
|
|
ilike,
|
|
asc,
|
|
desc,
|
|
and,
|
|
count,
|
|
inArray
|
|
} from "drizzle-orm"
|
|
|
|
import {
|
|
vendors,
|
|
contacts,
|
|
files,
|
|
} from "../../../db/schema"
|
|
|
|
export default async function vendorRoutes(server: FastifyInstance) {
|
|
|
|
// -------------------------------------------------------------
|
|
// LIST
|
|
// -------------------------------------------------------------
|
|
server.get("/resource/vendors", async (req, reply) => {
|
|
try {
|
|
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 baseQuery = server.db
|
|
.select()
|
|
.from(vendors)
|
|
.where(eq(vendors.tenant, tenantId))
|
|
|
|
// 🔎 Suche
|
|
if (search) {
|
|
baseQuery = server.db
|
|
.select()
|
|
.from(vendors)
|
|
.where(
|
|
and(
|
|
eq(vendors.tenant, tenantId),
|
|
ilike(vendors.name, `%${search}%`)
|
|
)
|
|
)
|
|
}
|
|
|
|
// 🔽 Sortierung
|
|
if (sort) {
|
|
const field = (vendors as any)[sort]
|
|
if (field) {
|
|
// @ts-ignore
|
|
baseQuery = baseQuery.orderBy(
|
|
ascQuery === "true" ? asc(field) : desc(field)
|
|
)
|
|
}
|
|
}
|
|
|
|
return await baseQuery
|
|
} catch (err) {
|
|
console.error("ERROR /resource/vendors:", err)
|
|
return reply.code(500).send({ error: "Internal Server Error" })
|
|
}
|
|
})
|
|
|
|
|
|
// -------------------------------------------------------------
|
|
// PAGINATED
|
|
// -------------------------------------------------------------
|
|
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" })
|
|
}
|
|
|
|
const queryConfig = req.queryConfig
|
|
const {
|
|
pagination,
|
|
sort,
|
|
filters,
|
|
paginationDisabled
|
|
} = queryConfig
|
|
|
|
const {
|
|
select,
|
|
search,
|
|
searchColumns,
|
|
distinctColumns
|
|
} = req.query as {
|
|
select?: string
|
|
search?: string
|
|
searchColumns?: string
|
|
distinctColumns?: string
|
|
}
|
|
|
|
// ----------------------------
|
|
// WHERE CONDITIONS
|
|
// ----------------------------
|
|
let whereCond: any = eq(vendors.tenant, tenantId)
|
|
|
|
if (filters) {
|
|
for (const [key, val] of Object.entries(filters)) {
|
|
const col = (vendors as any)[key]
|
|
if (!col) continue
|
|
|
|
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))
|
|
}
|
|
}
|
|
}
|
|
|
|
if (search && search.trim().length > 0) {
|
|
whereCond = and(
|
|
whereCond,
|
|
ilike(vendors.name, `%${search.trim().toLowerCase()}%`)
|
|
)
|
|
}
|
|
|
|
// ----------------------------
|
|
// COUNT
|
|
// ----------------------------
|
|
const totalRes = await server.db
|
|
.select({ value: count(vendors.id) })
|
|
.from(vendors)
|
|
.where(whereCond)
|
|
|
|
const total = Number(totalRes[0]?.value ?? 0)
|
|
|
|
// ----------------------------
|
|
// DISTINCT FILTER VALUES
|
|
// ----------------------------
|
|
const distinctValues: Record<string, any[]> = {}
|
|
|
|
if (distinctColumns) {
|
|
for (const colName of distinctColumns.split(",").map(v => v.trim())) {
|
|
const col = (vendors as any)[colName]
|
|
if (!col) continue
|
|
|
|
const rows = await server.db
|
|
.select({ v: col })
|
|
.from(vendors)
|
|
.where(eq(vendors.tenant, tenantId))
|
|
|
|
const values = rows
|
|
.map(r => r.v)
|
|
.filter(v => v != null && v !== "")
|
|
|
|
distinctValues[colName] = [...new Set(values)].sort()
|
|
}
|
|
}
|
|
|
|
// ----------------------------
|
|
// PAGINATION
|
|
// ----------------------------
|
|
let offset = 0
|
|
let limit = 999999
|
|
|
|
if (!paginationDisabled && pagination) {
|
|
offset = pagination.offset
|
|
limit = pagination.limit
|
|
}
|
|
|
|
// ----------------------------
|
|
// ORDER BY
|
|
// ----------------------------
|
|
let orderField = null
|
|
let orderDirection: "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"
|
|
}
|
|
}
|
|
|
|
// ----------------------------
|
|
// QUERY DATA
|
|
// ----------------------------
|
|
let dataQuery = server.db
|
|
.select()
|
|
.from(vendors)
|
|
.where(whereCond)
|
|
.offset(offset)
|
|
.limit(limit)
|
|
|
|
if (orderField) {
|
|
dataQuery =
|
|
orderDirection === "asc"
|
|
? dataQuery.orderBy(asc(orderField))
|
|
: dataQuery.orderBy(desc(orderField))
|
|
}
|
|
|
|
const data = await dataQuery
|
|
|
|
const totalPages = pagination?.limit
|
|
? Math.ceil(total / pagination.limit)
|
|
: 1
|
|
|
|
const enrichedConfig = {
|
|
...queryConfig,
|
|
total,
|
|
totalPages,
|
|
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" })
|
|
}
|
|
})
|
|
|
|
|
|
|
|
// -------------------------------------------------------------
|
|
// DETAIL (mit JOINS)
|
|
// -------------------------------------------------------------
|
|
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
|
|
.select()
|
|
.from(vendors)
|
|
.where(
|
|
and(
|
|
eq(vendors.id, vendorId),
|
|
eq(vendors.tenant, tenantId)
|
|
)
|
|
)
|
|
.limit(1)
|
|
|
|
if (!vendorRecord.length) {
|
|
return reply.code(404).send({ error: "Vendor not found" })
|
|
}
|
|
|
|
const vendor = vendorRecord[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)),
|
|
])
|
|
|
|
return {
|
|
...vendor,
|
|
contacts: vendorContacts,
|
|
files: vendorFiles,
|
|
}
|
|
} catch (err) {
|
|
console.error("ERROR /resource/vendors/:id:", err)
|
|
return reply.code(500).send({ error: "Internal Server Error" })
|
|
}
|
|
})
|
|
}
|