This commit is contained in:
2025-09-12 18:29:13 +02:00
parent dc385b8422
commit c98394b5bf
17 changed files with 1786 additions and 9 deletions

View File

@@ -2,11 +2,11 @@ import Fastify from "fastify";
import swaggerPlugin from "./plugins/swagger"
import supabasePlugin from "./plugins/supabase";
import healthRoutes from "./routes/health";
import meRoutes from "./routes/me";
import meRoutes from "./routes/auth/me";
import tenantRoutes from "./routes/tenant";
import tenantPlugin from "./plugins/tenant";
import authRoutes from "./routes/auth";
import authRoutesAuthenticated from "./routes/auth-authenticated";
import authRoutes from "./routes/auth/auth";
import authRoutesAuthenticated from "./routes/auth/auth-authenticated";
import authPlugin from "./plugins/auth";
import adminRoutes from "./routes/admin";
import corsPlugin from "./plugins/cors";
@@ -15,6 +15,11 @@ import resourceRoutesSpecial from "./routes/resourcesSpecial";
import fastifyCookie from "@fastify/cookie";
import historyRoutes from "./routes/history";
import fileRoutes from "./routes/files";
import userRoutes from "./routes/auth/user"
import functionRoutes from "./routes/functions";
import bankingRoutes from "./routes/banking";
import exportRoutes from "./routes/exports"
import emailAsUserRoutes from "./routes/emailAsUser";
import {sendMail} from "./utils/mailer";
@@ -52,6 +57,11 @@ async function main() {
await subApp.register(resourceRoutesSpecial);
await subApp.register(historyRoutes);
await subApp.register(fileRoutes);
await subApp.register(userRoutes);
await subApp.register(functionRoutes);
await subApp.register(bankingRoutes);
await subApp.register(exportRoutes);
await subApp.register(emailAsUserRoutes);
},{prefix: "/api"})

View File

@@ -1,8 +1,8 @@
import { FastifyInstance } from "fastify";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
import { generateRandomPassword, hashPassword } from "../utils/password"
import { sendMail } from "../utils/mailer"
import { generateRandomPassword, hashPassword } from "../../utils/password"
import { sendMail } from "../../utils/mailer"
export default async function authRoutes(server: FastifyInstance) {
// Registrierung

View File

@@ -25,7 +25,7 @@ export default async function meRoutes(server: FastifyInstance) {
// 2. Tenants laden (alle Tenants des Users)
const { data: tenantLinks, error: tenantLinksError } = await server.supabase
.from("auth_users")
.select(`*, tenants!auth_tenant_users ( id, name, locked )`)
.select(`*, tenants!auth_tenant_users ( id, name,short, locked, extraModules, businessInfo, numberRanges, dokuboxkey )`)
.eq("id", authUser.user_id)
.single();

67
src/routes/auth/user.ts Normal file
View File

@@ -0,0 +1,67 @@
import { FastifyInstance } from "fastify";
export default async function userRoutes(server: FastifyInstance) {
//TODO: PERMISSIONS Rückmeldung beschränken
server.get("/user/:id", async (req, reply) => {
const authUser = req.user // kommt aus JWT (user_id + tenant_id)
const {id} = req.params
if (!authUser) {
return reply.code(401).send({ error: "Unauthorized" })
}
// 1. User laden
const { data: user, error: userError } = await server.supabase
.from("auth_users")
.select("id, email, created_at, must_change_password")
.eq("id", id)
.single()
if (userError || !user) {
return reply.code(401).send({ error: "User not found" })
}
// 2. Tenants laden (alle Tenants des Users)
/*const { data: tenantLinks, error: tenantLinksError } = await server.supabase
.from("auth_users")
.select(`*, tenants!auth_tenant_users ( id, name, locked )`)
.eq("id", authUser.user_id)
.single();
if (tenantLinksError) {
console.log(tenantLinksError)
return reply.code(401).send({ error: "Tenant Error" })
}
const tenants = tenantLinks?.tenants*/
// 3. Aktiven Tenant bestimmen
const activeTenant = authUser.tenant_id /*|| tenants[0].id*/
// 4. Profil für den aktiven Tenant laden
let profile = null
if (activeTenant) {
const { data: profileData } = await server.supabase
.from("auth_profiles")
.select("*")
.eq("user_id", id)
.eq("tenant_id", activeTenant)
.single()
profile = profileData
}
// 5. Permissions laden (über Funktion)
// 6. Response zurückgeben
return {
user,
profile,
}
})
}

72
src/routes/banking.ts Normal file
View File

@@ -0,0 +1,72 @@
import { FastifyInstance } from "fastify";
import jwt from "jsonwebtoken";
import {insertHistoryItem} from "../utils/history";
export default async function bankingRoutes(server: FastifyInstance) {
//Create Banking Statement
server.post("/banking/statements", async (req, reply) => {
if (!req.user) {
return reply.code(401).send({ error: "Unauthorized" });
}
const body = req.body as { data: string };
console.log(body);
const {data,error} = await server.supabase.from("statementallocations").insert({
...body.data,
tenant: req.user.tenant_id,
}).select()
await insertHistoryItem(server,{
entity: "bankstatements",
entityId: data.id,
action: "created",
created_by: req.user.user_id,
tenant_id: req.user.tenant_id,
oldVal: null,
newVal: data,
text: `Buchung erstellt`,
});
if(data && !error){
return reply.send(data)
}
});
//Delete Banking Statement
server.delete("/banking/statements/:id", async (req, reply) => {
if (!req.user) {
return reply.code(401).send({ error: "Unauthorized" });
}
const { id } = req.params as { id?: string }
const {data} = await server.supabase.from("statementallocations").select().eq("id",id).single()
const {error} = await server.supabase.from("statementallocations").delete().eq("id",id)
if(!error){
await insertHistoryItem(server,{
entity: "bankstatements",
entityId: id,
action: "deleted",
created_by: req.user.user_id,
tenant_id: req.user.tenant_id,
oldVal: data,
newVal: null,
text: `Buchung gelöscht`,
});
return reply.send({success:true})
} else {
return reply.code(500).send({error:"Fehler beim löschen"})
}
})
}

30
src/routes/emailAsUser.ts Normal file
View File

@@ -0,0 +1,30 @@
import { FastifyInstance } from "fastify";
import {createInvoicePDF} from "../utils/pdf";
import {useNextNumberRangeNumber} from "../utils/functions";
import {sendMailAsUser} from "../utils/emailengine";
import {subtle} from "node:crypto";
export default async function emailAsUserRoutes(server: FastifyInstance) {
server.post("/emailasuser/send", async (req, reply) => {
const body = req.body as {
to: string
cc?: string
bcc?: string
subject?: string
text?: string
html?: string
attachments?: any,
account: string
}
try {
reply.send(await sendMailAsUser(body.to,body.subject,body.html,body.text,body.account,body.cc,body.bcc,body.attachments))
} catch (err) {
console.log(err)
reply.code(500).send({ error: "Failed to send E-Mail as User" })
}
})
}

105
src/routes/exports.ts Normal file
View File

@@ -0,0 +1,105 @@
import { FastifyInstance } from "fastify";
import jwt from "jsonwebtoken";
import {insertHistoryItem} from "../utils/history";
import {buildExportZip} from "../utils/export/datev";
import {s3} from "../utils/s3";
import {GetObjectCommand, PutObjectCommand} from "@aws-sdk/client-s3"
import {getSignedUrl} from "@aws-sdk/s3-request-presigner";
import dayjs from "dayjs";
import {randomUUID} from "node:crypto";
const createExport = async (server:FastifyInstance,req:any,startDate,endDate,beraternr,mandantennr) => {
console.log(startDate,endDate,beraternr,mandantennr)
// 1) ZIP erzeugen
const buffer = await buildExportZip(server,req.user.tenant_id, startDate, endDate, beraternr, mandantennr)
console.log("ZIP created")
// 2) Dateiname & Key festlegen
const fileKey = `${req.user.tenant_id}/exports/Export_${dayjs(startDate).format("YYYY-MM-DD")}_${dayjs(endDate).format("YYYY-MM-DD")}_${randomUUID()}.zip`
console.log(fileKey)
// 3) In S3 hochladen
await s3.send(
new PutObjectCommand({
Bucket: process.env.S3_BUCKET || "FEDEO",
Key: fileKey,
Body: buffer,
ContentType: "application/zip",
})
)
// 4) Presigned URL erzeugen (24h gültig)
const url = await getSignedUrl(
s3,
new GetObjectCommand({
Bucket: process.env.S3_BUCKET || "FEDEO",
Key: fileKey,
}),
{ expiresIn: 60 * 60 * 24 }
)
console.log(url)
// 5) In Supabase-DB speichern
const { data, error } = await server.supabase
.from("exports")
.insert([
{
tenant_id: req.user.tenant_id,
start_date: startDate,
end_date: endDate,
valid_until: dayjs().add(24,"hours").toISOString(),
file_path: fileKey,
url: url,
created_at: new Date().toISOString(),
},
])
.select()
.single()
console.log(data)
console.log(error)
}
export default async function exportRoutes(server: FastifyInstance) {
//Export DATEV
server.post("/exports/datev", async (req, reply) => {
const { start_date, end_date, beraternr, mandantennr } = req.body as {
start_date: string
end_date: string
beraternr: string
mandantennr: string
}
reply.send({success:true})
setImmediate(async () => {
try {
await createExport(server,req,start_date,end_date,beraternr,mandantennr)
console.log("Job done ✅")
} catch (err) {
console.error("Job failed ❌", err)
}
})
})
//List Exports Available for Download
server.get("/exports", async (req,reply) => {
const {data,error} = await server.supabase.from("exports").select().eq("tenant_id",req.user.tenant_id)
console.log(data,error)
reply.send(data)
})
}

View File

@@ -43,7 +43,6 @@ export default async function fileRoutes(server: FastifyInstance) {
if (!data.file) return reply.code(400).send({ error: "No file uploaded" })
console.log("ENDE")
const {data:createdFileData,error:createdFileError} = await server.supabase
.from("files")
@@ -207,7 +206,7 @@ export default async function fileRoutes(server: FastifyInstance) {
}
try {
const {data:supabaseFileEntries,error} = await server.supabase.from("files").select("*").in("id", ids)
const {data:supabaseFileEntries,error} = await server.supabase.from("files").select("*, createddocument(*, customer(*))").in("id", ids)

42
src/routes/functions.ts Normal file
View File

@@ -0,0 +1,42 @@
import { FastifyInstance } from "fastify";
import {createInvoicePDF} from "../utils/pdf";
import {useNextNumberRangeNumber} from "../utils/functions";
export default async function functionRoutes(server: FastifyInstance) {
server.post("/functions/createinvoicepdf", async (req, reply) => {
const body = req.body as {
invoiceData: any
backgroundPath?: string
}
try {
const pdf = await createInvoicePDF(
server,
"base64",
body.invoiceData,
body.backgroundPath
)
reply.send(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" })
}
}
)
}

View File

@@ -60,5 +60,101 @@ export default async function routes(server: FastifyInstance) {
return { token };
});
server.get("/tenant/users", async (req, reply) => {
const { tenant_id } = req.params as { tenant_id: string };
const authUser = req.user // kommt aus JWT (user_id + tenant_id)
if (!authUser) {
return reply.code(401).send({ error: "Unauthorized" })
}
const { data, error } = await server.supabase
.from("auth_tenant_users")
.select(`
user_id,
auth_users!tenantusers_user_id_fkey ( id, email, created_at, auth_profiles(*))`)
.eq("tenant_id", authUser.tenant_id);
if (error) {
console.log(error);
return reply.code(400).send({ error: error.message });
}
let correctedData = data.map(i => {
return {
id: i.user_id,
email: i.auth_users.email,
profile: i.auth_users.auth_profiles.find(x => x.tenant_id === authUser.tenant_id),
full_name: i.auth_users.auth_profiles.find(x => x.tenant_id === authUser.tenant_id)?.full_name,
}
})
return { tenant_id, users: correctedData };
});
server.put("/tenant/numberrange/:numberrange", async (req, reply) => {
if (!req.user) {
return reply.code(401).send({ error: "Unauthorized" });
}
const { numberrange } = req.params as { numberrange?: string }
const body = req.body as { numberRange: object };
console.log(body);
if(!body.numberRange) {
return reply.code(400).send({ error: "numberRange required" });
}
const {data:currentTenantData,error:numberRangesError} = await server.supabase.from("tenants").select().eq("id", req.user.tenant_id).single()
console.log(currentTenantData)
console.log(numberRangesError)
let numberRanges = {
// @ts-ignore
...currentTenantData.numberRanges
}
// @ts-ignore
numberRanges[numberrange] = body.numberRange
console.log(numberRanges)
const {data,error} = await server.supabase
.from("tenants")
.update({numberRanges: numberRanges})
.eq('id',req.user.tenant_id)
.select()
if(data && !error) {
return reply.send(data)
}
});
server.put("/tenant/other/:id", async (req, reply) => {
if (!req.user) {
return reply.code(401).send({ error: "Unauthorized" });
}
const { id } = req.params as { id?: string }
const body = req.body as { data: object };
console.log(body);
if(!body.data) {
return reply.code(400).send({ error: "data required" });
}
const {data:dataReturn,error} = await server.supabase
.from("tenants")
.update(body.data)
.eq('id',req.user.tenant_id)
.select()
if(dataReturn && !error) {
return reply.send(dataReturn)
}
});
}

45
src/utils/emailengine.ts Normal file
View File

@@ -0,0 +1,45 @@
import axios from "axios"
const AxiosEE = axios.create({
baseURL: process.env.EMAILENGINE_URL ||"https://ee.fedeo.io/v1",
headers: {
Authorization: `Bearer ${process.env.EMAILENGINE_TOKEN || "dcd8209bc5371c728f9ec951600afcfc74e8c391a7e984b2a6df9c4665dc7ad6"}`,
Accept: "application/json",
},
})
export async function sendMailAsUser(
to: string,
subject: string,
html: string,
text: string,
account: string,
cc: string,
bcc: string,
attachments: any,
): Promise<{ success: boolean; info?: any; error?: any }> {
try {
const sendData = {
to: to.split(";").map(i => { return {address: i}}),
cc: cc ? cc.split(";").map((i:any) => { return {address: i}}) : null,
bcc: bcc ? bcc.split(";").map((i:any) => { return {address: i}}) : null,
subject,
text,
html,
attachments
}
if(sendData.cc === null) delete sendData.cc
if(sendData.bcc === null) delete sendData.bcc
const {data} = await AxiosEE.post(`/account/${account}/submit`, sendData)
return { success: true, info: data }
} catch (err) {
console.error("❌ Fehler beim Mailversand:", err)
return { success: false, error: err }
}
}

382
src/utils/export/datev.ts Normal file
View File

@@ -0,0 +1,382 @@
import xmlbuilder from "xmlbuilder";
import dayjs from "dayjs";
import isBetween from "dayjs/plugin/isBetween.js"
import {BlobWriter, Data64URIReader, TextReader, TextWriter, ZipWriter} from "@zip.js/zip.js";
import {FastifyInstance} from "fastify";
import {GetObjectCommand} from "@aws-sdk/client-s3";
import {s3} from "../s3";
dayjs.extend(isBetween)
const getCreatedDocumentTotal = (item) => {
let totalNet = 0
let total19 = 0
let total7 = 0
item.rows.forEach(row => {
if(!['pagebreak','title','text'].includes(row.mode)){
let rowPrice = Number(Number(row.quantity) * Number(row.price) * (1 - Number(row.discountPercent) /100) ).toFixed(3)
totalNet = totalNet + Number(rowPrice)
if(row.taxPercent === 19) {
total19 = total19 + Number(rowPrice * 0.19)
} else if(row.taxPercent === 7) {
total7 = total7 + Number(rowPrice * 0.07)
}
}
})
let totalGross = Number(totalNet.toFixed(2)) + Number(total19.toFixed(2)) + Number(total7.toFixed(2))
return {
totalNet: totalNet,
total19: total19,
total7: total7,
totalGross: totalGross,
}
}
const escapeString = (str) => {
str = (str ||"")
.replaceAll("\n","")
.replaceAll(";","")
.replaceAll(/\r/g,"")
.replaceAll(/"/g,"")
.replaceAll(/ü/g,"ue")
.replaceAll(/ä/g,"ae")
.replaceAll(/ö/g,"oe")
return str
}
const displayCurrency = (input, onlyAbs = false) => {
if(onlyAbs) {
return Math.abs(input).toFixed(2).replace(".",",")
} else {
return input.toFixed(2).replace(".",",")
}
}
export async function buildExportZip(server: FastifyInstance, tenant: number, startDate: string, endDate: string, beraternr: string, mandantennr: string): Promise<Buffer> {
try {
const zipFileWriter = new BlobWriter()
const zipWriter = new ZipWriter(zipFileWriter)
//Basic Information
let header = `"EXTF";700;21;"Buchungsstapel";13;${dayjs().format("YYYYMMDDHHmmssSSS")};;"FE";"Florian Federspiel";;${beraternr};${mandantennr};20250101;4;${dayjs(startDate).format("YYYYMMDD")};${dayjs(endDate).format("YYYYMMDD")};"Buchungsstapel";"FF";1;0;1;"EUR";;"";;;"03";;;"";""`
let colHeaders = `Umsatz;Soll-/Haben-Kennzeichen;WKZ Umsatz;Kurs;Basisumsatz;WKZ Basisumsatz;Konto;Gegenkonto;BU-Schluessel;Belegdatum;Belegfeld 1;Belegfeld 2;Skonto;Buchungstext;Postensperre;Diverse Adressnummer;Geschaeftspartnerbank;Sachverhalt;Zinssperre;Beleglink;Beleginfo - Art 1;Beleginfo - Inhalt 1;Beleginfo - Art 2;Beleginfo - Inhalt 2;Beleginfo - Art 3;Beleginfo - Inhalt 3;Beleginfo - Art 4;Beleginfo - Inhalt 4;Beleginfo - Art 5;Beleginfo - Inhalt 5;Beleginfo - Art 6;Beleginfo - Inhalt 6;Beleginfo - Art 7;Beleginfo - Inhalt 7;Beleginfo - Art 8;Beleginfo - Inhalt 8;KOST1 - Kostenstelle;KOST2 - Kostenstelle;Kost Menge;EU-Land u. USt-IdNr. (Bestimmung);EU-Steuersatz (Bestimmung);Abw. Versteuerungsart;Sachverhalt L+L;Funktionsergaenzung L+L;BU 49 Hauptfunktionstyp;BU 49 Hauptfunktionsnummer;BU 49 Funktionsergaenzung;Zusatzinformation - Art 1;Zusatzinformation - Inhalt 1;Zusatzinformation - Art 2;Zusatzinformation - Inhalt 2;Zusatzinformation - Art 3;Zusatzinformation - Inhalt 3;Zusatzinformation - Art 4;Zusatzinformation - Inhalt 4;Zusatzinformation - Art 5;Zusatzinformation - Inhalt 5;Zusatzinformation - Art 6;Zusatzinformation - Inhalt 6;Zusatzinformation - Art 7;Zusatzinformation - Inhalt 7;Zusatzinformation - Art 8;Zusatzinformation - Inhalt 8;Zusatzinformation - Art 9;Zusatzinformation - Inhalt 9;Zusatzinformation - Art 10;Zusatzinformation - Inhalt 10;Zusatzinformation - Art 11;Zusatzinformation - Inhalt 11;Zusatzinformation - Art 12;Zusatzinformation - Inhalt 12;Zusatzinformation - Art 13;Zusatzinformation - Inhalt 13;Zusatzinformation - Art 14;Zusatzinformation - Inhalt 14;Zusatzinformation - Art 15;Zusatzinformation - Inhalt 15;Zusatzinformation - Art 16;Zusatzinformation - Inhalt 16;Zusatzinformation - Art 17;Zusatzinformation - Inhalt 17;Zusatzinformation - Art 18;Zusatzinformation - Inhalt 18;Zusatzinformation - Art 19;Zusatzinformation - Inhalt 19;Zusatzinformation - Art 20;Zusatzinformation - Inhalt 20;Stueck;Gewicht;Zahlweise;Zahlweise;Veranlagungsjahr;Zugeordnete Faelligkeit;Skontotyp;Auftragsnummer;Buchungstyp;USt-Schluessel (Anzahlungen);EU-Mitgliedstaat (Anzahlungen);Sachverhalt L+L (Anzahlungen);EU-Steuersatz (Anzahlungen);Erloeskonto (Anzahlungen);Herkunft-Kz;Leerfeld;KOST-Datum;SEPA-Mandatsreferenz;Skontosperre;Gesellschaftername;Beteiligtennummer;Identifikationsnummer;Zeichnernummer;Postensperre bis;Bezeichnung SoBil-Sachverhalt;Kennzeichen SoBil-Buchung;Festschreibung;Leistungsdatum;Datum Zuord. Steuerperiode;Faelligkeit;Generalumkehr;Steuersatz;Land;Abrechnungsreferenz;BVV-Position;EU-Mitgliedstaat u. UStID(Ursprung);EU-Steuersatz(Ursprung);Abw. Skontokonto`
//Get Bookings
const {data:statementallocationsRaw,error: statementallocationsError} = await server.supabase.from("statementallocations").select('*, account(*), bs_id(*, account(*)), cd_id(*,customer(*)), ii_id(*, vendor(*)), vendor(*), customer(*), ownaccount(*)').eq("tenant", tenant);
let {data:createddocumentsRaw,error: createddocumentsError} = await server.supabase.from("createddocuments").select('*,customer(*)').eq("tenant", tenant).in("type",["invoices","advanceInvoices","cancellationInvoices"]).eq("state","Gebucht")
let {data:incominginvoicesRaw,error: incominginvoicesError} = await server.supabase.from("incominginvoices").select('*, vendor(*)').eq("tenant", tenant)
const {data:accounts} = await server.supabase.from("accounts").select()
const {data:tenantData} = await server.supabase.from("tenants").select().eq("id",tenant).single()
let createddocuments = createddocumentsRaw.filter(i => dayjs(i.documentDate).isBetween(startDate,endDate,"day","[]"))
let incominginvoices = incominginvoicesRaw.filter(i => dayjs(i.date).isBetween(startDate,endDate,"day","[]"))
let statementallocations = statementallocationsRaw.filter(i => dayjs(i.bs_id.date).isBetween(startDate,endDate,"day","[]"))
const {data:filesCreateddocuments, error: filesErrorCD} = await server.supabase.from("files").select().eq("tenant",tenant).or(`createddocument.in.(${createddocuments.map(i => i.id).join(",")})`)
const {data:filesIncomingInvoices, error: filesErrorII} = await server.supabase.from("files").select().eq("tenant",tenant).or(`incominginvoice.in.(${incominginvoices.map(i => i.id).join(",")})`)
const downloadFile = async (bucketName, filePath, downloadFilePath,fileId) => {
const command = new GetObjectCommand({
Bucket: process.env.S3_BUCKET || "FEDEO",
Key: filePath,
})
const { Body, ContentType } = await s3.send(command)
const chunks: any[] = []
// @ts-ignore
for await (const chunk of Body) {
chunks.push(chunk)
}
const buffer = Buffer.concat(chunks)
const dataURL = `data:application/pdf;base64,${buffer.toString('base64')}`
const dataURLReader = new Data64URIReader(dataURL)
await zipWriter.add(`${fileId}.${downloadFilePath.split(".").pop()}`, dataURLReader)
//await fs.writeFile(`./output/${fileId}.${downloadFilePath.split(".").pop()}`, buffer, () => {});
console.log(`File added to Zip`);
};
for (const file of filesCreateddocuments) {
await downloadFile("filesdev",file.path,`./output/files/${file.path.split("/")[file.path.split("/").length - 1]}`,file.id);
}
for (const file of filesIncomingInvoices) {
await downloadFile("filesdev",file.path,`./output/files/${file.path.split("/")[file.path.split("/").length - 1]}`,file.id);
}
let bookingLines = []
createddocuments.forEach(createddocument => {
let file = filesCreateddocuments.find(i => i.createddocument === createddocument.id);
let total = 0
let typeString = ""
if(createddocument.type === "invoices") {
total = getCreatedDocumentTotal(createddocument).totalGross
console.log()
if(createddocument.usedAdvanceInvoices.length > 0){
createddocument.usedAdvanceInvoices.forEach(usedAdvanceInvoice => {
total -= getCreatedDocumentTotal(createddocumentsRaw.find(i => i.id === usedAdvanceInvoice)).totalGross
})
}
console.log(total)
typeString = "AR"
} else if(createddocument.type === "advanceInvoices") {
total = getCreatedDocumentTotal(createddocument).totalGross
typeString = "ARAbschlag"
} else if(createddocument.type === "cancellationInvoices") {
total = getCreatedDocumentTotal(createddocument).totalGross
typeString = "ARStorno"
}
let shSelector = "S"
if(Math.sign(total) === 1) {
shSelector = "S"
} else if (Math.sign(total) === -1) {
shSelector = "H"
}
bookingLines.push(`${displayCurrency(total,true)};"${shSelector}";;;;;${createddocument.customer.customerNumber};8400;"";${dayjs(createddocument.documentDate).format("DDMM")};"${createddocument.documentNumber}";;;"${`${typeString} ${createddocument.documentNumber} - ${createddocument.customer.name}`.substring(0,59)}";;;;;;${file ? `"BEDI ""${file.id}"""` : ""};"Geschäftspartner";"${createddocument.customer.name}";"Kundennummer";"${createddocument.customer.customerNumber}";"Belegnummer";"${createddocument.documentNumber}";"Leistungsdatum";"${dayjs(createddocument.deliveryDate).format("DD.MM.YYYY")}";"Belegdatum";"${dayjs(createddocument.documentDate).format("DD.MM.YYYY")}";;;;;;;;;;"";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0;;;;"";;;;;;;`)
})
incominginvoices.forEach(incominginvoice => {
console.log(incominginvoice.id);
incominginvoice.accounts.forEach(account => {
let file = filesIncomingInvoices.find(i => i.incominginvoice === incominginvoice.id);
let accountData = accounts.find(i => i.id === account.account)
let buschluessel = 9
if(account.taxType === '19'){
buschluessel = 9
} else if(account.taxType === 'null') {
buschluessel = ""
} else if(account.taxType === '7') {
buschluessel = "8"
} else if(account.taxType === '19I') {
buschluessel = "19"
} else if(account.taxType === '7I') {
buschluessel = "18"
} else {
buschluessel = "-"
}
let shSelector = "S"
let amountGross = account.amountGross ? account.amountGross : account.amountNet + account.amountTax
if(Math.sign(amountGross) === 1) {
shSelector = "S"
} else if(Math.sign(amountGross) === -1) {
shSelector = "H"
}
let text = `ER ${incominginvoice.reference}: ${escapeString(incominginvoice.description)}`.substring(0,59)
console.log(incominginvoice)
bookingLines.push(`${Math.abs(amountGross).toFixed(2).replace(".",",")};"${shSelector}";;;;;${accountData.number};${incominginvoice.vendor.vendorNumber};"${buschluessel}";${dayjs(incominginvoice.date).format("DDMM")};"${incominginvoice.reference}";;;"${text}";;;;;;${file ? `"BEDI ""${file.id}"""` : ""};"Geschäftspartner";"${incominginvoice.vendor.name}";"Kundennummer";"${incominginvoice.vendor.vendorNumber}";"Belegnummer";"${incominginvoice.reference}";"Leistungsdatum";"${dayjs(incominginvoice.date).format("DD.MM.YYYY")}";"Belegdatum";"${dayjs(incominginvoice.date).format("DD.MM.YYYY")}";;;;;;;;;;"";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0;;;;"";;;;;;;`)
})
})
statementallocations.forEach(statementallocation => {
let shSelector = "S"
if(Math.sign(statementallocation.amount) === 1) {
shSelector = "S"
} else if(Math.sign(statementallocation.amount) === -1) {
shSelector = "H"
}
if(statementallocation.cd_id) {
bookingLines.push(`${displayCurrency(statementallocation.amount,true)};"H";;;;;${statementallocation.cd_id.customer.customerNumber};${statementallocation.bs_id.account.datevNumber};"3";${dayjs(statementallocation.cd_id.documentDate).format("DDMM")};"${statementallocation.cd_id.documentNumber}";;;"${`ZE${statementallocation.description}${escapeString(statementallocation.bs_id.text)}`.substring(0,59)}";;;;;;;"Geschäftspartner";"${statementallocation.cd_id.customer.name}";"Kundennummer";"${statementallocation.cd_id.customer.customerNumber}";"Belegnummer";"${statementallocation.cd_id.documentNumber}";"Leistungsdatum";"${dayjs(statementallocation.cd_id.deliveryDate).format("DD.MM.YYYY")}";"Belegdatum";"${dayjs(statementallocation.cd_id.documentDate).format("DD.MM.YYYY")}";;;;;;;;;;"";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0;;;;"";;;;;;;`)
} else if(statementallocation.ii_id) {
bookingLines.push(`${displayCurrency(statementallocation.amount,true)};"${shSelector}";;;;;${statementallocation.bs_id.account.datevNumber};${statementallocation.ii_id.vendor.vendorNumber};"";${dayjs(statementallocation.ii_id.date).format("DDMM")};"${statementallocation.ii_id.reference}";;;"${`ZA${statementallocation.description} ${escapeString(statementallocation.bs_id.text)} `.substring(0,59)}";;;;;;;"Geschäftspartner";"${statementallocation.ii_id.vendor.name}";"Kundennummer";"${statementallocation.ii_id.vendor.vendorNumber}";"Belegnummer";"${statementallocation.ii_id.reference}";"Leistungsdatum";"${dayjs(statementallocation.ii_id.date).format("DD.MM.YYYY")}";"Belegdatum";"${dayjs(statementallocation.ii_id.date).format("DD.MM.YYYY")}";;;;;;;;;;"";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0;;;;"";;;;;;;`)
} else if(statementallocation.account) {
bookingLines.push(`${displayCurrency(statementallocation.amount,true)};"${shSelector}";;;;;${statementallocation.bs_id.account.datevNumber};${statementallocation.account.number};"";${dayjs(statementallocation.bs_id.date).format("DDMM")};"";;;"${`${Math.sign(statementallocation.amount) > 0 ? "ZE" : "ZA"} ${statementallocation.account.number} - ${escapeString(statementallocation.account.label)}${escapeString(statementallocation.description)}${statementallocation.bs_id.text}`.substring(0,59)}";;;;;;;"Geschäftspartner";"${statementallocation.bs_id.credName}";"Kundennummer";"";"Belegnummer";"";"Leistungsdatum";"";"Belegdatum";"${dayjs(statementallocation.bs_id.date).format("DD.MM.YYYY")}";;;;;;;;;;"";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0;;;;"";;;;;;;`)
} else if(statementallocation.vendor) {
bookingLines.push(`${displayCurrency(statementallocation.amount,true)};"${shSelector}";;;;;${statementallocation.bs_id.account.datevNumber};${statementallocation.vendor.vendorNumber};"";${dayjs(statementallocation.bs_id.date).format("DDMM")};"";;;"${`${Math.sign(statementallocation.amount) > 0 ? "ZE" : "ZA"} ${statementallocation.vendor.vendorNumber} - ${escapeString(statementallocation.vendor.name)}${escapeString(statementallocation.description)}${statementallocation.bs_id.text}`.substring(0,59)}";;;;;;;"Geschäftspartner";"${statementallocation.vendor.name}";"Kundennummer";"";"Belegnummer";"";"Leistungsdatum";"";"Belegdatum";"${dayjs(statementallocation.bs_id.date).format("DD.MM.YYYY")}";;;;;;;;;;"";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0;;;;"";;;;;;;`)
} else if(statementallocation.customer) {
bookingLines.push(`${displayCurrency(statementallocation.amount,true)};"${shSelector}";;;;;${statementallocation.bs_id.account.datevNumber};${statementallocation.customer.customerNumber};"";${dayjs(statementallocation.bs_id.date).format("DDMM")};"";;;"${`${Math.sign(statementallocation.amount) > 0 ? "ZE" : "ZA"} ${statementallocation.customer.customerNumber} - ${escapeString(statementallocation.customer.name)}${escapeString(statementallocation.description)}${statementallocation.bs_id.text}`.substring(0,59)}";;;;;;;"Geschäftspartner";"${statementallocation.customer.name}";"Kundennummer";"";"Belegnummer";"";"Leistungsdatum";"";"Belegdatum";"${dayjs(statementallocation.bs_id.date).format("DD.MM.YYYY")}";;;;;;;;;;"";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0;;;;"";;;;;;;`)
} else if(statementallocation.ownaccount) {
bookingLines.push(`${displayCurrency(statementallocation.amount,true)};"${shSelector}";;;;;${statementallocation.bs_id.account.datevNumber};${statementallocation.ownaccount.number};"";${dayjs(statementallocation.bs_id.date).format("DDMM")};"";;;"${`${Math.sign(statementallocation.amount) > 0 ? "ZE" : "ZA"} ${statementallocation.ownaccount.number} - ${escapeString(statementallocation.ownaccount.name)}${escapeString(statementallocation.description)}${statementallocation.bs_id.text}`.substring(0,59)}";;;;;;;"Geschäftspartner";"${statementallocation.ownaccount.name}";"Kundennummer";"";"Belegnummer";"";"Leistungsdatum";"";"Belegdatum";"${dayjs(statementallocation.bs_id.date).format("DD.MM.YYYY")}";;;;;;;;;;"";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;0;;;;"";;;;;;;`)
}
})
let csvString = `${header}\n${colHeaders}\n`;
bookingLines.forEach(line => {
csvString += `${line}\n`;
})
const buchungsstapelReader = new TextReader(csvString)
await zipWriter.add(`EXTF_Buchungsstapel_von_${dayjs(startDate).format("DDMMYYYY")}_bis_${dayjs(endDate).format("DDMMYYYY")}.csv`, buchungsstapelReader)
/*fs.writeFile(`output/EXTF_Buchungsstapel_von_${dayjs(startDate).format("DDMMYYYY")}_bis_${dayjs(endDate).format("DDMMYYYY")}.csv`, csvString, 'utf8', function (err) {
if (err) {
console.log('Some error occured - file either not saved or corrupted file saved.');
console.log(err);
} else{
console.log('It\'s saved!');
}
});*/
// Kreditoren/Debitoren
let headerStammdaten = `"EXTF";700;16;"Debitoren/Kreditoren";5;${dayjs().format("YYYYMMDDHHmmssSSS")};;"FE";"Florian Federspiel";;${beraternr};${mandantennr};20250101;4;${dayjs(startDate).format("YYYYMMDD")};${dayjs(endDate).format("YYYYMMDD")};"Debitoren & Kreditoren";"FF";1;0;1;"EUR";;"";;;"03";;;"";""`
let colHeadersStammdaten = `Konto;Name (Adressattyp Unternehmen);Unternehmensgegenstand;Name (Adressattyp natuerl. Person);Vorname (Adressattyp natuerl. Person);Name (Adressattyp keine Angabe);Adressatentyp;Kurzbezeichnung;EU-Land;EU-UStID;Anrede;Titel/Akad. Grad;Adelstitel;Namensvorsatz;Adressart;Strasse;Postfach;Postleitzahl;Ort;Land;Versandzusatz;Adresszusatz;Abweichende Anrede;Abw. Zustellbezeichnung 1;Abw. Zustellbezeichnung 2;Kennz. Korrespondenzadresse;Adresse Gueltig von;Adresse Gueltig bis;Telefon;Bemerkung (Telefon);Telefon GL;Bemerkung (Telefon GL);E-Mail;Bemerkung (E-Mail);Internet;Bemerkung (Internet);Fax;Bemerkung (Fax);Sonstige;Bemerkung (Sonstige);Bankleitzahl 1;Bankbezeichnung 1;Bankkonto-Nummer 1;Laenderkennzeichen 1;IBAN-Nr. 1;Leerfeld;SWIFT-Code 1;Abw. Kontoinhaber 1;Kennz. Haupt-Bankverb. 1;Bankverb. 1 Gueltig von;Bankverb. 1 Gueltig bis;Bankleitzahl 2;Bankbezeichnung 2;Bankkonto-Nummer 2;Laenderkennzeichen 2;IBAN-Nr. 2;Leerfeld;SWIFT-Code 2;Abw. Kontoinhaber 2;Kennz. Haupt-Bankverb. 2;Bankverb. 2 gueltig von;Bankverb. 2 gueltig bis;Bankleitzahl 3;Bankbezeichnung 3;Bankkonto-Nummer 3;Laenderkennzeichen 3;IBAN-Nr. 3;Leerfeld;SWIFT-Code 3;Abw. Kontoinhaber 3;Kennz. Haupt-Bankverb. 3;Bankverb. 3 gueltig von;Bankverb. 3 gueltig bis;Bankleitzahl 4;Bankbezeichnung 4;Bankkonto-Nummer 4;Laenderkennzeichen 4;IBAN-Nr. 4;Leerfeld;SWIFT-Code 4;Abw. Kontoinhaber 4;Kennz. Haupt-Bankverb. 4;Bankverb. 4 gueltig von;Bankverb. 4 gueltig bis;Bankleitzahl 5;Bankbezeichnung 5;Bankkonto-Nummer 5;Laenderkennzeichen 5;IBAN-Nr. 5;Leerfeld;SWIFT-Code 5;Abw. Kontoinhaber 5;Kennz. Haupt-Bankverb. 5;Bankverb. 5 gueltig von;Bankverb. 5 gueltig bis;Leerfeld;Briefanrede;Grussformel;Kunden-/Lief.-Nr.;Steuernummer;Sprache;Ansprechpartner;Vertreter;Sachbearbeiter;Diverse-Konto;Ausgabeziel;Waehrungssteuerung;Kreditlimit (Debitor);Zahlungsbedingung;Faelligkeit in Tagen (Debitor);Skonto in Prozent (Debitor);Kreditoren-Ziel 1 Tg.;Kreditoren-Skonto 1 %;Kreditoren-Ziel 2 Tg.;Kreditoren-Skonto 2 %;Kreditoren-Ziel 3 Brutto Tg.;Kreditoren-Ziel 4 Tg.;Kreditoren-Skonto 4 %;Kreditoren-Ziel 5 Tg.;Kreditoren-Skonto 5 %;Mahnung;Kontoauszug;Mahntext 1;Mahntext 2;Mahntext 3;Kontoauszugstext;Mahnlimit Betrag;Mahnlimit %;Zinsberechnung;Mahnzinssatz 1;Mahnzinssatz 2;Mahnzinssatz 3;Lastschrift;Verfahren;Mandantenbank;Zahlungstraeger;Indiv. Feld 1;Indiv. Feld 2;Indiv. Feld 3;Indiv. Feld 4;Indiv. Feld 5;Indiv. Feld 6;Indiv. Feld 7;Indiv. Feld 8;Indiv. Feld 9;Indiv. Feld 10;Indiv. Feld 11;Indiv. Feld 12;Indiv. Feld 13;Indiv. Feld 14;Indiv. Feld 15;Abweichende Anrede (Rechnungsadresse);Adressart (Rechnungsadresse);Strasse (Rechnungsadresse);Postfach (Rechnungsadresse);Postleitzahl (Rechnungsadresse);Ort (Rechnungsadresse);Land (Rechnungsadresse);Versandzusatz (Rechnungsadresse);Adresszusatz (Rechnungsadresse);Abw. Zustellbezeichnung 1 (Rechnungsadresse);Abw. Zustellbezeichnung 2 (Rechnungsadresse);Adresse Gueltig von (Rechnungsadresse);Adresse Gueltig bis (Rechnungsadresse);Bankleitzahl 6;Bankbezeichnung 6;Bankkonto-Nummer 6;Laenderkennzeichen 6;IBAN-Nr. 6;Leerfeld;SWIFT-Code 6;Abw. Kontoinhaber 6;Kennz. Haupt-Bankverb. 6;Bankverb. 6 gueltig von;Bankverb. 6 gueltig bis;Bankleitzahl 7;Bankbezeichnung 7;Bankkonto-Nummer 7;Laenderkennzeichen 7;IBAN-Nr. 7;Leerfeld;SWIFT-Code 7;Abw. Kontoinhaber 7;Kennz. Haupt-Bankverb. 7;Bankverb. 7 gueltig von;Bankverb. 7 gueltig bis;Bankleitzahl 8;Bankbezeichnung 8;Bankkonto-Nummer 8;Laenderkennzeichen 8;IBAN-Nr. 8;Leerfeld;SWIFT-Code 8;Abw. Kontoinhaber 8;Kennz. Haupt-Bankverb. 8;Bankverb. 8 gueltig von;Bankverb. 8 gueltig bis;Bankleitzahl 9;Bankbezeichnung 9;Bankkonto-Nummer 9;Laenderkennzeichen 9;IBAN-Nr. 9;Leerfeld;SWIFT-Code 9;Abw. Kontoinhaber 9;Kennz. Haupt-Bankverb. 9;Bankverb. 9 gueltig von;Bankverb. 9 gueltig bis;Bankleitzahl 10;Bankbezeichnung 10;Bankkonto-Nummer 10;Laenderkennzeichen 10;IBAN-Nr. 10;Leerfeld;SWIFT-Code 10;Abw. Kontoinhaber 10;Kennz. Haupt-Bankverb. 10;Bankverb 10 Gueltig von;Bankverb 10 Gueltig bis;Nummer Fremdsystem;Insolvent;SEPA-Mandatsreferenz 1;SEPA-Mandatsreferenz 2;SEPA-Mandatsreferenz 3;SEPA-Mandatsreferenz 4;SEPA-Mandatsreferenz 5;SEPA-Mandatsreferenz 6;SEPA-Mandatsreferenz 7;SEPA-Mandatsreferenz 8;SEPA-Mandatsreferenz 9;SEPA-Mandatsreferenz 10;Verknuepftes OPOS-Konto;Mahnsperre bis;Lastschriftsperre bis;Zahlungssperre bis;Gebuehrenberechnung;Mahngebuehr 1;Mahngebuehr 2;Mahngebuehr 3;Pauschalberechnung;Verzugspauschale 1;Verzugspauschale 2;Verzugspauschale 3;Alternativer Suchname;Status;Anschrift manuell geaendert (Korrespondenzadresse);Anschrift individuell (Korrespondenzadresse);Anschrift manuell geaendert (Rechnungsadresse);Anschrift individuell (Rechnungsadresse);Fristberechnung bei Debitor;Mahnfrist 1;Mahnfrist 2;Mahnfrist 3;Letzte Frist`
const {data:customers} = await server.supabase.from("customers").select().eq("tenant",tenant).order("customerNumber")
const {data:vendors} = await server.supabase.from("vendors").select().eq("tenant",tenant).order("vendorNumber")
let bookinglinesStammdaten = []
customers.forEach(customer => {
bookinglinesStammdaten.push(`${customer.customerNumber};"${customer.isCompany ? customer.name.substring(0,48): ''}";;"${!customer.isCompany ? (customer.lastname ? customer.lastname : customer.name) : ''}";"${!customer.isCompany ? (customer.firstname ? customer.firstname : '') : ''}";;${customer.isCompany ? 2 : 1};;;;;;;;"STR";"${customer.infoData.street ? customer.infoData.street : ''}";;"${customer.infoData.zip ? customer.infoData.zip : ''}";"${customer.infoData.city ? customer.infoData.city : ''}";;;"${customer.infoData.special ? customer.infoData.special : ''}";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;`)
})
vendors.forEach(vendor => {
bookinglinesStammdaten.push(`${vendor.vendorNumber};"${vendor.name.substring(0,48)}";;;;;2;;;;;;;;"STR";"${vendor.infoData.street ? vendor.infoData.street : ''}";;"${vendor.infoData.zip ? vendor.infoData.zip : ''}";"${vendor.infoData.city ? vendor.infoData.city : ''}";;;"${vendor.infoData.special ? vendor.infoData.special : ''}";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;`)
})
let csvStringStammdaten = `${headerStammdaten}\n${colHeadersStammdaten}\n`;
bookinglinesStammdaten.forEach(line => {
csvStringStammdaten += `${line}\n`;
})
const stammdatenReader = new TextReader(csvStringStammdaten)
await zipWriter.add(`EXTF_Stammdaten_von_${dayjs(startDate).format("DDMMYYYY")}_bis_${dayjs(endDate).format("DDMMYYYY")}.csv`, stammdatenReader)
/*fs.writeFile(`output/EXTF_Stammdaten_von_${dayjs(startDate).format("DDMMYYYY")}_bis_${dayjs(endDate).format("DDMMYYYY")}.csv`, csvStringStammdaten, 'utf8', function (err) {
if (err) {
console.log('Some error occured - file either not saved or corrupted file saved.');
console.log(err);
} else{
console.log('It\'s saved!');
}
});*/
//Sachkonten
let headerSachkonten = `"EXTF";700;20;"Kontenbeschriftungen";3;${dayjs().format("YYYYMMDDHHmmssSSS")};;"FE";"Florian Federspiel";;${beraternr};${mandantennr};20250101;4;${dayjs(startDate).format("YYYYMMDD")};${dayjs(endDate).format("YYYYMMDD")};"Sachkonten";"FF";1;0;1;"EUR";;"";;;"03";;;"";""`
let colHeadersSachkonten = `Konto;Kontenbeschriftung;Sprach-ID;Kontenbeschriftung lang`
const {data:bankaccounts} = await server.supabase.from("bankaccounts").select().eq("tenant",tenant).order("datevNumber")
let bookinglinesSachkonten = []
bankaccounts.forEach(bankaccount => {
bookinglinesSachkonten.push(`${bankaccount.datevNumber};"${bankaccount.name}";"de-DE";`)
})
let csvStringSachkonten = `${headerSachkonten}\n${colHeadersSachkonten}\n`;
bookinglinesSachkonten.forEach(line => {
csvStringSachkonten += `${line}\n`;
})
const sachkontenReader = new TextReader(csvStringSachkonten)
await zipWriter.add(`EXTF_Sachkonten_von_${dayjs(startDate).format("DDMMYYYY")}_bis_${dayjs(endDate).format("DDMMYYYY")}.csv`, sachkontenReader)
/*fs.writeFile(`output/EXTF_Sachkonten_von_${dayjs(startDate).format("DDMMYYYY")}_bis_${dayjs(endDate).format("DDMMYYYY")}.csv`, csvStringSachkonten, 'utf8', function (err) {
if (err) {
console.log('Some error occured - file either not saved or corrupted file saved.');
console.log(err);
} else{
console.log('It\'s saved!');
}
});*/
let obj = {
archive: {
'@version':"5.0",
"@generatingSystem":"fedeo.de",
"@xsi:schemaLocation":"http://xml.datev.de/bedi/tps/document/v05.0 Document_v050.xsd",
"@xmlns":"http://xml.datev.de/bedi/tps/document/v05.0",
"@xmlns:xsi":"http://www.w3.org/2001/XMLSchema-instance",
header: {
date: dayjs().format("YYYY-MM-DDTHH:mm:ss")
},
content: {
document: []
}
}
}
filesCreateddocuments.forEach(file => {
obj.archive.content.document.push({
"@guid": file.id,
extension: {
"@xsi:type":"File",
"@name":`${file.id}.pdf`
}
})
})
filesIncomingInvoices.forEach(file => {
obj.archive.content.document.push({
"@guid": file.id,
extension: {
"@xsi:type":"File",
"@name":`${file.id}.pdf`
}
})
})
let doc = xmlbuilder.create(obj, {encoding: 'UTF-8', standalone: true})
//console.log(doc.end({pretty: true}));
const documentsReader = new TextReader(doc.end({pretty: true}))
await zipWriter.add(`document.xml`, documentsReader)
/*function toBuffer(arrayBuffer) {
const buffer = Buffer.alloc(arrayBuffer.byteLength);
const view = new Uint8Array(arrayBuffer);
for (let i = 0; i < buffer.length; ++i) {
buffer[i] = view[i];
}
return buffer;
}*/
const arrayBuffer = await (await zipWriter.close()).arrayBuffer()
return Buffer.from(arrayBuffer)
} catch(error) {
console.log(error)
}
}

23
src/utils/functions.ts Normal file
View File

@@ -0,0 +1,23 @@
import {FastifyInstance} from "fastify";
export const useNextNumberRangeNumber = async (server:FastifyInstance, tenantId:number,numberRange)=> {
const {data:tenant} = await server.supabase.from("tenants").select().eq("id",tenantId).single()
const numberRanges = tenant.numberRanges
const usedNumber = (numberRanges[numberRange].prefix ? numberRanges[numberRange].prefix : "") + numberRanges[numberRange].nextNumber + (numberRanges[numberRange].suffix ? numberRanges[numberRange].suffix : "")
let newNumberRange = numberRanges
newNumberRange[numberRange].nextNumber += 1
const {error} = await server.supabase.from("tenants").update({numberRanges: newNumberRange}).eq("id",tenantId)
if(error) {
console.log(error)
} else {
return {
usedNumber
}
}
}

View File

@@ -43,7 +43,8 @@ export async function insertHistoryItem(
spaces: "space",
trackingtrips: "trackingtrip",
createddocuments: "createddocument",
inventoryitemgroups: "inventoryitemgroup"
inventoryitemgroups: "inventoryitemgroup",
bankstatements: "bankstatement"
}
const fkColumn = columnMap[params.entity]

854
src/utils/pdf.ts Normal file
View File

@@ -0,0 +1,854 @@
import {PDFDocument, StandardFonts, rgb} from "pdf-lib"
import dayjs from "dayjs"
import {renderAsCurrency, splitStringBySpace} from "./stringRendering";
import {FastifyInstance} from "fastify";
const getCoordinatesForPDFLib = (x:number ,y:number, page:any) => {
/*
* @param x the wanted X Parameter in Millimeters from Top Left
* @param y the wanted Y Parameter in Millimeters from Top Left
* @param page the page Object
*
* @returns x,y object
* */
let retX = x * 2.83
let retY = page.getHeight()-(y*2.83)
return {
x: retX,
y: retY
}
}
export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoiceData, backgroundPath:string) => {
console.log(returnMode, invoiceData, backgroundPath)
const genPDF = async (invoiceData, backgroundSourceBuffer) => {
const pdfDoc = await PDFDocument.create()
const font = await pdfDoc.embedFont(StandardFonts.Helvetica)
const fontBold = await pdfDoc.embedFont(StandardFonts.HelveticaBold)
let pages = []
let pageCounter = 1
//const backgroundPdfSourceBuffer = await fetch("/Briefpapier.pdf").then((res) => res.arrayBuffer())
const backgroudPdf = await PDFDocument.load(backgroundSourceBuffer)
const firstPageBackground = await pdfDoc.embedPage(backgroudPdf.getPages()[0])
const secondPageBackground = await pdfDoc.embedPage(backgroudPdf.getPages()[backgroudPdf.getPages().length > 1 ? 1 : 0])
//
const page1 = pdfDoc.addPage()
//
page1.drawPage(firstPageBackground, {
x: 0,
y: 0,
})
//
pages.push(page1)
//
//Falzmarke 1
pages[pageCounter - 1].drawLine({
start: getCoordinatesForPDFLib(0, 105, page1),
end: getCoordinatesForPDFLib(5, 105, page1),
thickness: 0.2,
color: rgb(0, 0, 0),
opacity: 1
})
//Lochmarke
pages[pageCounter - 1].drawLine({
start: getCoordinatesForPDFLib(0, 148.5, page1),
end: getCoordinatesForPDFLib(5, 148.5, page1),
thickness: 0.2,
color: rgb(0, 0, 0),
opacity: 1
})
//Falzmarke 2
pages[pageCounter - 1].drawLine({
start: getCoordinatesForPDFLib(0, 210, page1),
end: getCoordinatesForPDFLib(5, 210, page1),
thickness: 0.2,
color: rgb(0, 0, 0),
opacity: 1
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,45,page1),
end: getCoordinatesForPDFLib(105,45,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
if (!invoiceData.addressLine) console.log("Missing Addressline")
pages[pageCounter - 1].drawText(invoiceData.adressLine, {
...getCoordinatesForPDFLib(21, 48, page1),
size: 6,
color: rgb(0, 0, 0),
lineHeight: 6,
opacity: 1,
maxWidth: 240
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,50,page1),
end: getCoordinatesForPDFLib(105,50,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
let partLinesAdded = 0
invoiceData.recipient.forEach((info, index) => {
let maxSplitLength = 35
let splittedContent = splitStringBySpace(info, maxSplitLength)
splittedContent.forEach((part, partIndex) => {
if (partIndex === 0) {
pages[pageCounter - 1].drawText(part, {
...getCoordinatesForPDFLib(21, 55 + index * 5 + partLinesAdded * 5, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240
})
} else {
partLinesAdded++
pages[pageCounter - 1].drawText(part, {
...getCoordinatesForPDFLib(21, 55 + index * 5 + partLinesAdded * 5, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240
})
}
/*if(partIndex > 0) partLinesAdded++
pages[pageCounter - 1].drawText(part, {
y: getCoordinatesForPDFLib(21,55+index*5+partLinesAdded*5, page1).y,
x: getCoordinatesForPDFLib(21,55+index*5+partLinesAdded*5,page1).x + 230 - font.widthOfTextAtSize(part,10),
size:10,
color:rgb(0,0,0),
lineHeight:10,
opacity: 1,
maxWidth: 240
})*/
})
})
//Rechts
partLinesAdded = 0
invoiceData.info.forEach((info, index) => {
let maxSplitLength = 34
let splittedContent = splitStringBySpace(info.content, maxSplitLength)
splittedContent.forEach((part, partIndex) => {
if (partIndex === 0) {
pages[pageCounter - 1].drawText(info.label, {
...getCoordinatesForPDFLib(116, 55 + index * 5 + partLinesAdded * 5, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240
})
}
if (partIndex > 0) partLinesAdded++
pages[pageCounter - 1].drawText(part, {
y: getCoordinatesForPDFLib(116, 55 + index * 5 + partLinesAdded * 5, page1).y,
x: getCoordinatesForPDFLib(116, 55 + index * 5 + partLinesAdded * 5, page1).x + 230 - font.widthOfTextAtSize(part, 10),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240
})
})
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(125,90,page1),
end: getCoordinatesForPDFLib(200,90,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
//Title
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,95,page1),
end: getCoordinatesForPDFLib(200,95,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
if (!invoiceData.title) console.log("Missing Title")
pages[pageCounter - 1].drawText(invoiceData.title, {
...getCoordinatesForPDFLib(20, 100, page1),
size: 13,
color: rgb(0, 0, 0),
lineHeight: 15,
opacity: 1,
maxWidth: 500
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,105,page1),
end: getCoordinatesForPDFLib(200,105,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
if (!invoiceData.description) console.log("Missing Description")
if (invoiceData.description) {
pages[pageCounter - 1].drawText(invoiceData.description, {
...getCoordinatesForPDFLib(20, 112, page1),
size: 13,
color: rgb(0, 0, 0),
lineHeight: 15,
opacity: 1,
maxWidth: 500
})
}
if (!invoiceData.startText) console.log("Missing StartText")
pages[pageCounter - 1].drawText(invoiceData.startText, {
...getCoordinatesForPDFLib(20, 119, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 500
})
/*page1.drawLine({
start: getCoordinatesForPDFLib(20,115,page1),
end: getCoordinatesForPDFLib(200,115,page1),
thickness: 0.5,
color: rgb(0,0,0),
opacity: 1
})*/
pages[pageCounter - 1].drawLine({
start: getCoordinatesForPDFLib(20, 140, page1),
end: getCoordinatesForPDFLib(199, 140, page1),
thickness: 0.1,
color: rgb(0, 0, 0),
opacity: 1,
})
/*pages[pageCounter - 1].drawRectangle({
...getCoordinatesForPDFLib(20,140, page1),
width: 180 * 2.83,
height: 8 * 2.83,
color: rgb(0,0,0),
opacity: 0,
borderWidth: 0.1
})*/
//Header
pages[pageCounter - 1].drawText("Pos", {
...getCoordinatesForPDFLib(21, 137, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText("Menge", {
...getCoordinatesForPDFLib(35, 137, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText("Bezeichnung", {
...getCoordinatesForPDFLib(52, 137, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
if (invoiceData.type !== "deliveryNotes") {
pages[pageCounter - 1].drawText("Steuer", {
...getCoordinatesForPDFLib(135, 137, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText("Einheitspreis", {
...getCoordinatesForPDFLib(150, 137, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText("Gesamt", {
y: getCoordinatesForPDFLib(25, 137, page1).y,
x: getCoordinatesForPDFLib(25, 137, page1).x + 490 - fontBold.widthOfTextAtSize("Gesamt", 12),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
}
let rowHeight = 145.5
let pageIndex = 0
invoiceData.rows.forEach((row, index) => {
if (!["pagebreak", "title", "text"].includes(row.mode)) {
if (!row.pos) console.log("Missing Row Pos")
pages[pageCounter - 1].drawText(String(row.pos), {
...getCoordinatesForPDFLib(21, rowHeight, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240
})
if (!row.quantity) console.log("Missing Row Quantity")
if (!row.unit) console.log("Missing Row Unit")
pages[pageCounter - 1].drawText((row.optional || row.alternative) ? `(${row.quantity} ${row.unit})` : `${row.quantity} ${row.unit}`, {
...getCoordinatesForPDFLib(35, rowHeight, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240
})
let rowTextLines = 0
if (invoiceData.type !== "deliveryNotes") {
pages[pageCounter - 1].drawText(splitStringBySpace(row.text, 35).join("\n"), {
...getCoordinatesForPDFLib(52, rowHeight, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
font: fontBold
})
rowTextLines = splitStringBySpace(row.text, 35).length
} else {
pages[pageCounter - 1].drawText(splitStringBySpace(row.text, 80).join("\n"), {
...getCoordinatesForPDFLib(52, rowHeight, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
font: fontBold
})
rowTextLines = splitStringBySpace(row.text, 80).length
}
let rowDescriptionLines = 0
if (row.descriptionText) {
if (invoiceData.type !== "deliveryNotes") {
rowDescriptionLines = splitStringBySpace(row.descriptionText, 60).length
pages[pageCounter - 1].drawText(splitStringBySpace(row.descriptionText, 60).join("\n"), {
...getCoordinatesForPDFLib(52, rowHeight + (rowTextLines * 5), page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
})
} else {
rowDescriptionLines = splitStringBySpace(row.descriptionText, 80).length
pages[pageCounter - 1].drawText(splitStringBySpace(row.descriptionText, 80).join("\n"), {
...getCoordinatesForPDFLib(52, rowHeight + (rowTextLines * 5), page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
})
}
}
if (invoiceData.type !== "deliveryNotes") {
pages[pageCounter - 1].drawText(`${row.taxPercent} %`, {
...getCoordinatesForPDFLib(135, rowHeight, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText(row.price, {
...getCoordinatesForPDFLib(150, rowHeight, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240
})
pages[pageCounter - 1].drawText((row.optional || row.alternative) ? `(${row.rowAmount})` : row.rowAmount, {
y: getCoordinatesForPDFLib(25, rowHeight, page1).y,
x: getCoordinatesForPDFLib(25, rowHeight, page1).x + 490 - font.widthOfTextAtSize((row.optional || row.alternative) ? `(${row.rowAmount})` : row.rowAmount, 10),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240,
})
if (row.discountPercent > 0) {
let text = row.discountText
if (row.optional) text = `Optional - ${text}`
if (row.alternative) text = `Alternativ - ${text}`
pages[pageCounter - 1].drawText(text, {
y: getCoordinatesForPDFLib(25, rowHeight + 5, page1).y,
x: getCoordinatesForPDFLib(25, rowHeight + 5, page1).x + 490 - font.widthOfTextAtSize(text, 8),
size: 8,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240,
})
} else if (row.optional) {
pages[pageCounter - 1].drawText("Optional", {
y: getCoordinatesForPDFLib(25, rowHeight + 5, page1).y,
x: getCoordinatesForPDFLib(25, rowHeight + 5, page1).x + 490 - font.widthOfTextAtSize("Optional", 8),
size: 8,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240,
})
} else if (row.alternative) {
pages[pageCounter - 1].drawText("Alternativ", {
y: getCoordinatesForPDFLib(25, rowHeight + 5, page1).y,
x: getCoordinatesForPDFLib(25, rowHeight + 5, page1).x + 490 - font.widthOfTextAtSize("Alternativ", 8),
size: 8,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240,
})
}
}
if (row.descriptionText) {
rowHeight += rowDescriptionLines * 4.5 + rowTextLines * 5.5
} else if (row.discountPercent) {
rowHeight += (rowTextLines + 1) * 5.5
} else if (row.optional || row.alternative) {
rowHeight += (rowTextLines + 1) * 5.5
} else {
rowHeight += rowTextLines * 5.5
}
pageIndex += 1
} else if (row.mode === 'pagebreak') {
console.log(invoiceData.rows[index + 1])
if (invoiceData.rows[index + 1].mode === 'title') {
let transferSumText = `Übertrag: ${invoiceData.total.titleSumsTransfer[Object.keys(invoiceData.total.titleSums)[invoiceData.rows[index + 1].pos - 2]]}`
pages[pageCounter - 1].drawText(transferSumText, {
y: getCoordinatesForPDFLib(21, rowHeight - 2, page1).y,
x: getCoordinatesForPDFLib(21, rowHeight - 2, page1).x + 500 - fontBold.widthOfTextAtSize(transferSumText, 10),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240,
font: fontBold
})
}
const page = pdfDoc.addPage()
page.drawPage(secondPageBackground, {
x: 0,
y: 0,
})
//Falzmarke 1
page.drawLine({
start: getCoordinatesForPDFLib(0, 105, page1),
end: getCoordinatesForPDFLib(7, 105, page1),
thickness: 0.25,
color: rgb(0, 0, 0),
opacity: 1
})
//Lochmarke
page.drawLine({
start: getCoordinatesForPDFLib(0, 148.5, page1),
end: getCoordinatesForPDFLib(7, 148.5, page1),
thickness: 0.25,
color: rgb(0, 0, 0),
opacity: 1
})
//Falzmarke 2
page.drawLine({
start: getCoordinatesForPDFLib(0, 210, page1),
end: getCoordinatesForPDFLib(7, 210, page1),
thickness: 0.25,
color: rgb(0, 0, 0),
opacity: 1
})
page.drawText("Pos", {
...getCoordinatesForPDFLib(21, 22, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
page.drawText("Menge", {
...getCoordinatesForPDFLib(35, 22, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
page.drawText("Bezeichnung", {
...getCoordinatesForPDFLib(52, 22, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
if (invoiceData.type !== "deliveryNotes") {
page.drawText("Steuer", {
...getCoordinatesForPDFLib(135, 22, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
page.drawText("Einheitspreis", {
...getCoordinatesForPDFLib(150, 22, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
page.drawText("Gesamt", {
y: getCoordinatesForPDFLib(25, 22, page1).y,
x: getCoordinatesForPDFLib(25, 22, page1).x + 490 - fontBold.widthOfTextAtSize("Gesamt", 12),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 240,
font: fontBold
})
}
pageCounter += 1;
pageIndex = 0;
rowHeight = 30;
pages.push(page)
} else if (row.mode === 'title') {
if (index === 0 || pageIndex === 0) {
rowHeight += 3
} else {
let transferSumText = `Übertrag: ${invoiceData.total.titleSumsTransfer[Object.keys(invoiceData.total.titleSums)[row.pos - 2]]}`
pages[pageCounter - 1].drawText(transferSumText, {
y: getCoordinatesForPDFLib(21, rowHeight - 2, page1).y,
x: getCoordinatesForPDFLib(21, rowHeight - 2, page1).x + 500 - fontBold.widthOfTextAtSize(transferSumText, 10),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawLine({
start: getCoordinatesForPDFLib(20, rowHeight, page1),
end: getCoordinatesForPDFLib(199, rowHeight, page1),
thickness: 0.2,
color: rgb(0, 0, 0),
opacity: 1,
})
rowHeight += 5
}
pages[pageCounter - 1].drawText(String(row.pos), {
...getCoordinatesForPDFLib(21, rowHeight, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText(splitStringBySpace(row.text, 60).join("\n"), {
...getCoordinatesForPDFLib(35, rowHeight, page1),
size: 12,
color: rgb(0, 0, 0),
lineHeight: 12,
opacity: 1,
maxWidth: 500,
font: fontBold
})
rowHeight += splitStringBySpace(row.text, 60).length * 4.5
} else if (row.mode === 'text') {
if (index === 0 || pageIndex === 0) {
rowHeight += 3
}
if (row.descriptionText) {
pages[pageCounter - 1].drawText(splitStringBySpace(row.descriptionText, 70).join("\n"), {
...getCoordinatesForPDFLib(35, rowHeight, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
})
rowHeight += (splitStringBySpace(row.descriptionText, 70) || []).length * 4
rowHeight += 4
}
}
console.log(rowHeight)
})
let endTextDiff = 35
if (invoiceData.type !== "deliveryNotes") {
pages[pageCounter - 1].drawLine({
start: getCoordinatesForPDFLib(20, rowHeight, page1),
end: getCoordinatesForPDFLib(198, rowHeight, page1),
thickness: 0.2,
color: rgb(0, 0, 0),
opacity: 1,
})
rowHeight += 6
if (Object.keys(invoiceData.total.titleSums).length > 0) {
Object.keys(invoiceData.total.titleSums).forEach((key, index) => {
pages[pageCounter - 1].drawText(splitStringBySpace(key, 60).join("\n"), {
...getCoordinatesForPDFLib(21, rowHeight, page1),
size: 11,
color: rgb(0, 0, 0),
lineHeight: 11,
opacity: 1,
font: fontBold
})
pages[pageCounter - 1].drawText(invoiceData.total.titleSums[key], {
y: getCoordinatesForPDFLib(21, rowHeight, page1).y,
x: getCoordinatesForPDFLib(21, rowHeight, page1).x + 500 - fontBold.widthOfTextAtSize(invoiceData.total.titleSums[key], 11),
size: 11,
color: rgb(0, 0, 0),
lineHeight: 11,
opacity: 1,
maxWidth: 240,
font: fontBold
})
rowHeight += splitStringBySpace(key, 60).length * 5
})
/*let titleSumsArray = Object.keys(invoiceData.total.titleSums)
titleSumsArray.forEach(sum => {
let length = splitStringBySpace(sum,60).length
rowHeight += length *6
})*/
//rowHeight += Object.keys(invoiceData.total.titleSums)
pages[pageCounter - 1].drawLine({
start: getCoordinatesForPDFLib(20, rowHeight, page1),
end: getCoordinatesForPDFLib(198, rowHeight, page1),
thickness: 0.2,
color: rgb(0, 0, 0),
opacity: 1,
})
rowHeight += 5
}
invoiceData.totalArray.forEach((item, index) => {
pages[pageCounter - 1].drawText(item.label, {
...getCoordinatesForPDFLib(21, rowHeight + 8 * index, page1),
size: 11,
color: rgb(0, 0, 0),
lineHeight: 11,
opacity: 1,
maxWidth: 240,
font: fontBold
})
pages[pageCounter - 1].drawText(item.content, {
y: getCoordinatesForPDFLib(21, rowHeight + 8 * index, page1).y,
x: getCoordinatesForPDFLib(21, rowHeight + 8 * index, page1).x + 500 - fontBold.widthOfTextAtSize(item.content, 11),
size: 11,
color: rgb(0, 0, 0),
lineHeight: 11,
opacity: 1,
maxWidth: 240,
font: fontBold
})
})
if (invoiceData.taxType !== "13b UStG" && invoiceData.taxType !== "19 UStG" && invoiceData.taxType !== "12.3 UStG") {
} else {
if (invoiceData.taxType === "13b UStG") {
pages[pageCounter - 1].drawText("Die Umsatzsteuer für diese Leistung schuldet nach §13b UStG der Leistungsempfänger", {
...getCoordinatesForPDFLib(21, rowHeight + invoiceData.totalArray.length * 8, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 500
})
} else if (invoiceData.taxType === "19 UStG") {
pages[pageCounter - 1].drawText("Als Kleinunternehmer im Sinne von § 19 Abs. 1 UStG wird keine Umsatzsteuer berechnet.", {
...getCoordinatesForPDFLib(20, rowHeight + invoiceData.totalArray.length * 8, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 500
})
} else if (invoiceData.taxType === "12.3 UStG") {
pages[pageCounter - 1].drawText("Umsatzsteuer befreite Lieferung/Leistung für PV-Anlagen gemäß § 12 Absatz 3 UStG.", {
...getCoordinatesForPDFLib(20, rowHeight + invoiceData.totalArray.length * 8, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 500
})
}
}
pages[pageCounter - 1].drawText(invoiceData.endText, {
...getCoordinatesForPDFLib(21, rowHeight + endTextDiff + (invoiceData.totalArray.length - 3) * 8, page1),
size: 10,
color: rgb(0, 0, 0),
lineHeight: 10,
opacity: 1,
maxWidth: 500
})
return await pdfDoc.saveAsBase64()
}
}
const {data:backgroundPDFData,error:backgroundPDFError} = await server.supabase.storage.from("files").download(backgroundPath)
const pdfBytes = await genPDF(invoiceData, await backgroundPDFData.arrayBuffer())
if(returnMode === "base64"){
return {
mimeType: 'application/pdf',
base64: pdfBytes
}
}
}

View File

@@ -0,0 +1,51 @@
export const renderAsCurrency = (value: string | number,currencyString = "€") => {
return `${Number(value).toFixed(2).replace(".",",")} ${currencyString}`
}
export const splitStringBySpace = (input:string,maxSplitLength:number,removeLinebreaks = false) => {
if(removeLinebreaks) {
input = input.replaceAll("\n","")
}
let splitStrings: string[] = []
input.split("\n").forEach(string => {
splitStrings.push(string)
})
let returnSplitStrings: string[] = []
splitStrings.forEach(string => {
let regex = / /gi, result, indices = [];
while ( (result = regex.exec(string)) ) {
indices.push(result.index);
}
let lastIndex = 0
if(string.length > maxSplitLength) {
let tempStrings = []
for (let i = maxSplitLength; i < string.length; i = i + maxSplitLength) {
let nearestIndex = indices.length > 0 ? indices.reduce(function(prev, curr) {
return (Math.abs(curr - i) < Math.abs(prev - i) ? curr : prev);
}) : i
tempStrings.push(string.substring(lastIndex,nearestIndex))
lastIndex = indices.length > 0 ? nearestIndex + 1 : nearestIndex
}
tempStrings.push(string.substring(lastIndex,input.length))
returnSplitStrings.push(...tempStrings)
} else {
returnSplitStrings.push(string)
}
})
return returnSplitStrings
}