330 lines
9.8 KiB
TypeScript
330 lines
9.8 KiB
TypeScript
import { FastifyInstance } from "fastify"
|
|
import {
|
|
eq,
|
|
ilike,
|
|
asc,
|
|
desc,
|
|
and,
|
|
count,
|
|
inArray,
|
|
or,
|
|
} from "drizzle-orm"
|
|
|
|
import {
|
|
customers,
|
|
projects,
|
|
plants,
|
|
contracts,
|
|
contacts,
|
|
createddocuments,
|
|
statementallocations,
|
|
files,
|
|
events,
|
|
} 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) {
|
|
|
|
|
|
// -------------------------------------------------------------
|
|
// LIST
|
|
// -------------------------------------------------------------
|
|
server.get("/resource/customers", 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
|
|
}
|
|
|
|
// 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(whereCond)
|
|
|
|
// Sortierung
|
|
if (sort) {
|
|
const field = (customers as any)[sort]
|
|
if (field) {
|
|
//@ts-ignore
|
|
baseQuery = baseQuery.orderBy(
|
|
ascQuery === "true" ? asc(field) : desc(field)
|
|
)
|
|
}
|
|
}
|
|
|
|
return await baseQuery
|
|
})
|
|
|
|
|
|
|
|
// -------------------------------------------------------------
|
|
// PAGINATED
|
|
// -------------------------------------------------------------
|
|
server.get("/resource/customers/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 (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]
|
|
if (!col) continue
|
|
|
|
if (Array.isArray(val)) {
|
|
whereCond = and(whereCond, inArray(col, val))
|
|
} else {
|
|
whereCond = and(whereCond, eq(col, val as any))
|
|
}
|
|
}
|
|
}
|
|
|
|
// ----------------------------
|
|
// 🔍 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
|
|
.select({ value: count(customers.id) })
|
|
.from(customers)
|
|
.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 = (customers as any)[colName]
|
|
if (!col) continue
|
|
|
|
const rows = await server.db
|
|
.select({ v: col })
|
|
.from(customers)
|
|
.where(eq(customers.tenant, tenantId))
|
|
|
|
distinctValues[colName] =
|
|
[...new Set(rows.map(r => r.v).filter(v => v != null && v !== ""))]
|
|
.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 = (customers as any)[s.field]
|
|
if (col) {
|
|
orderField = col
|
|
orderDirection = s.direction === "asc" ? "asc" : "desc"
|
|
}
|
|
}
|
|
|
|
// ----------------------------
|
|
// QUERY DATA
|
|
// ----------------------------
|
|
let dataQuery = server.db
|
|
.select()
|
|
.from(customers)
|
|
.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
|
|
|
|
// ----------------------------
|
|
// CONFIG RESPONSE
|
|
// ----------------------------
|
|
const totalPages = pagination?.limit
|
|
? Math.ceil(total / pagination.limit)
|
|
: 1
|
|
|
|
const enrichedConfig = {
|
|
...queryConfig,
|
|
total,
|
|
totalPages,
|
|
distinctValues,
|
|
search: search || null,
|
|
}
|
|
|
|
return {
|
|
data,
|
|
queryConfig: enrichedConfig,
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.log(e)
|
|
}
|
|
})
|
|
|
|
|
|
|
|
// -------------------------------------------------------------
|
|
// DETAIL (mit ALLEN JOINS)
|
|
// -------------------------------------------------------------
|
|
server.get("/resource/customers/: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" })
|
|
|
|
// --- 1) Customer selbst laden
|
|
const customerRecord = await server.db
|
|
.select()
|
|
.from(customers)
|
|
.where(and(eq(customers.id, Number(id)), eq(customers.tenant, tenantId)))
|
|
.limit(1)
|
|
|
|
if (!customerRecord.length) {
|
|
return reply.code(404).send({ error: "Customer not found" })
|
|
}
|
|
|
|
const customer = customerRecord[0]
|
|
|
|
|
|
// --- 2) Relations:
|
|
const [
|
|
customerProjects,
|
|
customerPlants,
|
|
customerContracts,
|
|
customerContacts,
|
|
customerDocuments,
|
|
customerFiles,
|
|
customerEvents,
|
|
] = await Promise.all([
|
|
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({
|
|
...createddocuments,
|
|
allocations: statementallocations,
|
|
})
|
|
.from(createddocuments)
|
|
.leftJoin(
|
|
statementallocations,
|
|
eq(statementallocations.cd_id, createddocuments.id)
|
|
)
|
|
.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))),
|
|
])
|
|
|
|
return {
|
|
...customer,
|
|
projects: customerProjects,
|
|
plants: customerPlants,
|
|
contracts: customerContracts,
|
|
contacts: customerContacts,
|
|
createddocuments: customerDocuments,
|
|
files: customerFiles,
|
|
events: customerEvents,
|
|
}
|
|
})
|
|
}
|