245 lines
7.6 KiB
TypeScript
245 lines
7.6 KiB
TypeScript
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<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,
|
||
}
|
||
})
|
||
}
|