import { FastifyInstance } from "fastify"; import {createInvoicePDF, createTimeSheetPDF} from "../utils/pdf"; import {encodeBase64ToNiimbot, generateLabel, useNextNumberRangeNumber} from "../utils/functions"; import dayjs from "dayjs"; //import { ready as zplReady } from 'zpl-renderer-js' //import { renderZPL } from "zpl-image"; import customParseFormat from "dayjs/plugin/customParseFormat.js"; import isoWeek from "dayjs/plugin/isoWeek.js"; import isBetween from "dayjs/plugin/isBetween.js"; import isSameOrAfter from "dayjs/plugin/isSameOrAfter.js" import isSameOrBefore from "dayjs/plugin/isSameOrBefore.js" import duration from "dayjs/plugin/duration.js"; import timezone from "dayjs/plugin/timezone.js"; import {generateTimesEvaluation} from "../modules/time/evaluation.service"; import {citys} from "../../db/schema"; import {eq} from "drizzle-orm"; import {executeManualGeneration, finishManualGeneration} from "../modules/serialexecution.service"; dayjs.extend(customParseFormat) dayjs.extend(isoWeek) dayjs.extend(isBetween) dayjs.extend(isSameOrAfter) dayjs.extend(isSameOrBefore) dayjs.extend(duration) dayjs.extend(timezone) export default async function functionRoutes(server: FastifyInstance) { server.post("/functions/pdf/:type", async (req, reply) => { const body = req.body as { data: any backgroundPath?: string } const {type} = req.params as {type:string} try { let pdf = null if(type === "createdDocument") { pdf = await createInvoicePDF( server, "base64", body.data, body.backgroundPath ) } else if(type === "timesheet") { pdf = await createTimeSheetPDF( server, "base64", body.data, body.backgroundPath ) } return pdf // Fastify wandelt automatisch in JSON } catch (err) { console.log(err) reply.code(500).send({ error: "Failed to create PDF" }) } }) server.get("/functions/usenextnumber/:numberrange", async (req, reply) => { const { numberrange } = req.params as { numberrange: string }; const tenant = (req as any).user.tenant_id try { const result = await useNextNumberRangeNumber(server,tenant, numberrange) reply.send(result) // JSON automatisch } catch (err) { req.log.error(err) reply.code(500).send({ error: "Failed to generate next number" }) } }) /** * @route GET /functions/workingtimeevaluation/:user_id * @query start_date=YYYY-MM-DD * @query end_date=YYYY-MM-DD */ server.get("/functions/timeevaluation/:user_id", async (req, reply) => { const { user_id } = req.params as { user_id: string } const { start_date, end_date } = req.query as { start_date: string; end_date: string } const { tenant_id } = req.user // 🔒 Sicherheitscheck: andere User nur bei Berechtigung if (user_id !== req.user.user_id && !req.hasPermission("staff.time.read_all")) { return reply.code(403).send({ error: "Not allowed to view other users." }) } try { const result = await generateTimesEvaluation(server, user_id, tenant_id, start_date, end_date) reply.send(result) } catch (error) { console.error(error) reply.code(500).send({ error: error.message }) } }) server.get('/functions/check-zip/:zip', async (req, reply) => { const { zip } = req.params as { zip: string } const normalizedZip = String(zip || "").replace(/\D/g, "") if (normalizedZip.length !== 5) { return reply.code(400).send({ error: 'ZIP must contain exactly 5 digits' }) } try { const data = await server.db .select() .from(citys) .where(eq(citys.zip, Number(normalizedZip))) if (!data.length) { return reply.code(404).send({ error: 'ZIP not found' }) } const city = data[0] //districtMap const bundeslaender = [ { code: 'DE-BW', name: 'Baden-Württemberg' }, { code: 'DE-BY', name: 'Bayern' }, { code: 'DE-BE', name: 'Berlin' }, { code: 'DE-BB', name: 'Brandenburg' }, { code: 'DE-HB', name: 'Bremen' }, { code: 'DE-HH', name: 'Hamburg' }, { code: 'DE-HE', name: 'Hessen' }, { code: 'DE-MV', name: 'Mecklenburg-Vorpommern' }, { code: 'DE-NI', name: 'Niedersachsen' }, { code: 'DE-NW', name: 'Nordrhein-Westfalen' }, { code: 'DE-RP', name: 'Rheinland-Pfalz' }, { code: 'DE-SL', name: 'Saarland' }, { code: 'DE-SN', name: 'Sachsen' }, { code: 'DE-ST', name: 'Sachsen-Anhalt' }, { code: 'DE-SH', name: 'Schleswig-Holstein' }, { code: 'DE-TH', name: 'Thüringen' } ] return reply.send({ ...city, state_code: bundeslaender.find(i => i.name === city.countryName)?.code || null }) } catch (err) { console.log(err) return reply.code(500).send({ error: 'Internal server error' }) } }) server.post('/functions/serial/start', async (req, reply) => { console.log(req.body) const {executionDate,templateIds,tenantId} = req.body as {executionDate:string,templateIds:Number[],tenantId:Number} await executeManualGeneration(server,executionDate,templateIds,tenantId,req.user.user_id) }) server.post('/functions/serial/finish/:execution_id', async (req, reply) => { const {execution_id} = req.params as { execution_id: string } //@ts-ignore await finishManualGeneration(server,execution_id) }) server.post('/functions/services/bankstatementsync', async (req, reply) => { await server.services.bankStatements.run(req.user.tenant_id); }) server.post('/functions/services/prepareincominginvoices', async (req, reply) => { await server.services.prepareIncomingInvoices.run(req.user.tenant_id) }) server.post('/functions/services/syncdokubox', async (req, reply) => { await server.services.dokuboxSync.run() }) server.post('/print/label', async (req, reply) => { const { context, width = 584, height = 354 } = req.body as {context:any,width:number,height:number} try { const base64 = await generateLabel(context,width,height) return { encoded: await encodeBase64ToNiimbot(base64, 'top'), base64: base64 } } catch (err) { console.error('[Label Render Error]', err) return reply.code(500).send({ error: err.message || 'Failed to render label' }) } }) }