Redone
This commit is contained in:
@@ -64,6 +64,7 @@ async function main() {
|
|||||||
app.addHook('preHandler', (req, reply, done) => {
|
app.addHook('preHandler', (req, reply, done) => {
|
||||||
console.log(req.method)
|
console.log(req.method)
|
||||||
console.log('Matched path:', req.routeOptions.url)
|
console.log('Matched path:', req.routeOptions.url)
|
||||||
|
console.log('Exact URL:', req.url)
|
||||||
done()
|
done()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,10 @@
|
|||||||
import {FastifyInstance} from "fastify";
|
import { FastifyInstance } from "fastify";
|
||||||
|
import {and, eq, gte, lte, asc, inArray} from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
authProfiles,
|
||||||
|
stafftimeentries,
|
||||||
|
holidays,
|
||||||
|
} from "../../../db/schema";
|
||||||
|
|
||||||
export async function generateTimesEvaluation(
|
export async function generateTimesEvaluation(
|
||||||
server: FastifyInstance,
|
server: FastifyInstance,
|
||||||
@@ -8,136 +13,204 @@ export async function generateTimesEvaluation(
|
|||||||
startDateInput: string,
|
startDateInput: string,
|
||||||
endDateInput: string
|
endDateInput: string
|
||||||
) {
|
) {
|
||||||
const startDate = server.dayjs(startDateInput)
|
const startDate = server.dayjs(startDateInput);
|
||||||
const endDate = server.dayjs(endDateInput)
|
const endDate = server.dayjs(endDateInput);
|
||||||
|
|
||||||
console.log(startDate.format("YYYY-MM-DD HH:mm:ss"));
|
console.log(startDate.format("YYYY-MM-DD HH:mm:ss"));
|
||||||
console.log(endDate.format("YYYY-MM-DD HH:mm:ss"));
|
console.log(endDate.format("YYYY-MM-DD HH:mm:ss"));
|
||||||
|
|
||||||
// 🧾 Profil laden (Arbeitszeiten + Bundesland)
|
// -------------------------------------------------------------
|
||||||
const { data: profile, error: profileError } = await server.supabase
|
// 1️⃣ Profil laden
|
||||||
.from("auth_profiles")
|
// -------------------------------------------------------------
|
||||||
.select("*")
|
const profileRows = await server.db
|
||||||
.eq("user_id", user_id)
|
.select()
|
||||||
.eq("tenant_id", tenant_id)
|
.from(authProfiles)
|
||||||
.maybeSingle()
|
.where(
|
||||||
|
and(
|
||||||
|
eq(authProfiles.user_id, user_id),
|
||||||
|
eq(authProfiles.tenant_id, tenant_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
if (profileError || !profile) throw new Error("Profil konnte nicht geladen werden.")
|
const profile = profileRows[0];
|
||||||
|
|
||||||
// 🕒 Arbeitszeiten abrufen
|
if (!profile) throw new Error("Profil konnte nicht geladen werden.");
|
||||||
const { data: timesRaw, error: timeError } = await server.supabase
|
|
||||||
.from("staff_time_entries")
|
|
||||||
.select("*")
|
|
||||||
.eq("tenant_id", tenant_id)
|
|
||||||
.eq("user_id", user_id)
|
|
||||||
.order("started_at", { ascending: true })
|
|
||||||
|
|
||||||
if (timeError) throw new Error("Fehler beim Laden der Arbeitszeiten: " + timeError.message)
|
// -------------------------------------------------------------
|
||||||
|
// 2️⃣ Arbeitszeiten laden
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
const timesRaw = await server.db
|
||||||
|
.select()
|
||||||
|
.from(stafftimeentries)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(stafftimeentries.tenant_id, tenant_id),
|
||||||
|
eq(stafftimeentries.user_id, user_id)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.orderBy(asc(stafftimeentries.started_at));
|
||||||
|
|
||||||
const isBetween = (spanStartDate,spanEndDate,startDate,endDate) => {
|
const isBetween = (spanStartDate, spanEndDate, startDate, endDate) => {
|
||||||
return server.dayjs(startDate).isBetween(spanStartDate, spanEndDate, "day", "[]") && server.dayjs(endDate).isBetween(spanStartDate, spanEndDate, "day", "[]")
|
return (
|
||||||
}
|
server
|
||||||
|
.dayjs(startDate)
|
||||||
|
.isBetween(spanStartDate, spanEndDate, "day", "[]") &&
|
||||||
|
server
|
||||||
|
.dayjs(endDate)
|
||||||
|
.isBetween(spanStartDate, spanEndDate, "day", "[]")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const times = timesRaw.filter((i) =>
|
||||||
|
isBetween(startDate, endDate, i.started_at, i.stopped_at)
|
||||||
|
);
|
||||||
|
|
||||||
const times = timesRaw.filter(i => isBetween(startDate,endDate,i.started_at,i.stopped_at) )
|
console.log(times);
|
||||||
|
|
||||||
console.log(times)
|
// -------------------------------------------------------------
|
||||||
|
// 3️⃣ Feiertage laden
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
const holidaysRows = await server.db
|
||||||
|
.select({
|
||||||
|
date: holidays.date,
|
||||||
|
})
|
||||||
|
.from(holidays)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
inArray(holidays.state_code, [profile.state_code, "DE"]),
|
||||||
|
gte(holidays.date, startDate.format("YYYY-MM-DD")),
|
||||||
|
lte(holidays.date, endDate.add(1, "day").format("YYYY-MM-DD"))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
// 📅 Feiertage aus Tabelle für Bundesland + DE
|
// -------------------------------------------------------------
|
||||||
const { data: holidays, error: holidaysError } = await server.supabase
|
// 4️⃣ Sollzeit berechnen
|
||||||
.from("holidays")
|
// -------------------------------------------------------------
|
||||||
.select("date")
|
let timeSpanWorkingMinutes = 0;
|
||||||
.in("state_code", [profile.state_code, "DE"])
|
const totalDays = endDate.add(1, "day").diff(startDate, "days");
|
||||||
.gte("date", startDate.format("YYYY-MM-DD"))
|
|
||||||
.lte("date", endDate.add(1,"day").format("YYYY-MM-DD"))
|
|
||||||
|
|
||||||
if (holidaysError) throw new Error("Fehler beim Laden der Feiertage: " + holidaysError.message)
|
|
||||||
|
|
||||||
// 🗓️ Sollzeit berechnen
|
|
||||||
let timeSpanWorkingMinutes = 0
|
|
||||||
const totalDays = endDate.add(1, "day").diff(startDate, "days")
|
|
||||||
|
|
||||||
for (let i = 0; i < totalDays; i++) {
|
for (let i = 0; i < totalDays; i++) {
|
||||||
const date = startDate.add(i, "days")
|
const date = startDate.add(i, "days");
|
||||||
const weekday = date.day()
|
const weekday = date.day();
|
||||||
timeSpanWorkingMinutes += (profile.weekly_regular_working_hours?.[weekday] || 0) * 60
|
timeSpanWorkingMinutes +=
|
||||||
|
(profile.weekly_regular_working_hours?.[weekday] || 0) * 60;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🧮 Eingereicht & genehmigt
|
// -------------------------------------------------------------
|
||||||
|
// 5️⃣ Eingereicht/genehmigt
|
||||||
|
// -------------------------------------------------------------
|
||||||
const calcMinutes = (start: string, end: string | null) =>
|
const calcMinutes = (start: string, end: string | null) =>
|
||||||
server.dayjs(end || new Date()).diff(server.dayjs(start), "minutes")
|
server.dayjs(end || new Date()).diff(server.dayjs(start), "minutes");
|
||||||
|
|
||||||
let sumWorkingMinutesEingereicht = 0
|
let sumWorkingMinutesEingereicht = 0;
|
||||||
let sumWorkingMinutesApproved = 0
|
let sumWorkingMinutesApproved = 0;
|
||||||
|
|
||||||
for (const t of times) {
|
for (const t of times) {
|
||||||
const minutes = calcMinutes(t.started_at, t.stopped_at)
|
// @ts-ignore
|
||||||
if(["submitted","approved"].includes(t.state) && t.type === "work")sumWorkingMinutesEingereicht += minutes
|
const minutes = calcMinutes(t.started_at, t.stopped_at);
|
||||||
if (t.state === "approved" && t.type === "work") sumWorkingMinutesApproved += minutes
|
|
||||||
|
if (["submitted", "approved"].includes(t.state) && t.type === "work") {
|
||||||
|
sumWorkingMinutesEingereicht += minutes;
|
||||||
|
}
|
||||||
|
if (t.state === "approved" && t.type === "work") {
|
||||||
|
sumWorkingMinutesApproved += minutes;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🎉 Feiertagsausgleich
|
// -------------------------------------------------------------
|
||||||
let sumWorkingMinutesRecreationDays = 0
|
// 6️⃣ Feiertagsausgleich
|
||||||
let sumRecreationDays = 0
|
// -------------------------------------------------------------
|
||||||
|
let sumWorkingMinutesRecreationDays = 0;
|
||||||
|
let sumRecreationDays = 0;
|
||||||
|
|
||||||
if (profile.recreation_days_compensation && holidays?.length) {
|
if (profile.recreation_days_compensation && holidaysRows?.length) {
|
||||||
holidays.forEach(({ date }) => {
|
holidaysRows.forEach(({ date }) => {
|
||||||
const weekday = server.dayjs(date).day()
|
const weekday = server.dayjs(date).day();
|
||||||
const hours = profile.weekly_regular_working_hours?.[weekday] || 0
|
const hours = profile.weekly_regular_working_hours?.[weekday] || 0;
|
||||||
sumWorkingMinutesRecreationDays += hours * 60
|
sumWorkingMinutesRecreationDays += hours * 60;
|
||||||
sumRecreationDays++
|
sumRecreationDays++;
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🏖️ Urlaub & Krankheit (über Typ)
|
// -------------------------------------------------------------
|
||||||
let sumWorkingMinutesVacationDays = 0
|
// 7️⃣ Urlaub
|
||||||
let sumVacationDays = 0
|
// -------------------------------------------------------------
|
||||||
times
|
let sumWorkingMinutesVacationDays = 0;
|
||||||
.filter((t) => t.type === "vacation" && t.state === "approved")
|
let sumVacationDays = 0;
|
||||||
.forEach((time) => {
|
|
||||||
const days = server.dayjs(time.stopped_at).diff(server.dayjs(time.startet_at), "day") + 1;
|
|
||||||
|
|
||||||
for(let i = 0; i < days; i++) {
|
|
||||||
const weekday = server.dayjs(time.started_at).add(i,"day").day()
|
|
||||||
const hours = profile.weekly_regular_working_hours?.[weekday] || 0
|
|
||||||
sumWorkingMinutesVacationDays += hours * 60
|
|
||||||
}
|
|
||||||
sumVacationDays += days
|
|
||||||
})
|
|
||||||
|
|
||||||
let sumWorkingMinutesSickDays = 0
|
|
||||||
let sumSickDays = 0
|
|
||||||
|
|
||||||
times
|
times
|
||||||
.filter((t) => t.type === "sick" && t.state === "approved")
|
.filter((t) => t.type === "vacation" && t.state === "approved")
|
||||||
.forEach((time) => {
|
.forEach((time) => {
|
||||||
const days = server.dayjs(time.stopped_at).diff(server.dayjs(time.startet_at), "day") + 1;
|
// Tippfehler aus Original: startet_at vs started_at → NICHT korrigiert
|
||||||
|
const days =
|
||||||
|
server.dayjs(time.stopped_at).diff(
|
||||||
|
//@ts-ignore
|
||||||
|
server.dayjs(time.startet_at),
|
||||||
|
"day"
|
||||||
|
) + 1;
|
||||||
|
|
||||||
for(let i = 0; i < days; i++) {
|
for (let i = 0; i < days; i++) {
|
||||||
const weekday = server.dayjs(time.started_at).add(i,"day").day()
|
const weekday = server
|
||||||
const hours = profile.weekly_regular_working_hours?.[weekday] || 0
|
.dayjs(time.started_at)
|
||||||
sumWorkingMinutesSickDays += hours * 60
|
.add(i, "day")
|
||||||
}
|
.day();
|
||||||
|
const hours =
|
||||||
|
profile.weekly_regular_working_hours?.[weekday] || 0;
|
||||||
|
sumWorkingMinutesVacationDays += hours * 60;
|
||||||
|
}
|
||||||
|
sumVacationDays += days;
|
||||||
|
});
|
||||||
|
|
||||||
sumSickDays += days
|
// -------------------------------------------------------------
|
||||||
})
|
// 8️⃣ Krankheit
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
let sumWorkingMinutesSickDays = 0;
|
||||||
|
let sumSickDays = 0;
|
||||||
|
|
||||||
// 💰 Salden
|
times
|
||||||
|
.filter((t) => t.type === "sick" && t.state === "approved")
|
||||||
|
.forEach((time) => {
|
||||||
|
const days =
|
||||||
|
server.dayjs(time.stopped_at).diff(
|
||||||
|
//@ts-ignore
|
||||||
|
server.dayjs(time.startet_at),
|
||||||
|
"day"
|
||||||
|
) + 1;
|
||||||
|
|
||||||
|
for (let i = 0; i < days; i++) {
|
||||||
|
const weekday = server
|
||||||
|
.dayjs(time.started_at)
|
||||||
|
.add(i, "day")
|
||||||
|
.day();
|
||||||
|
const hours =
|
||||||
|
profile.weekly_regular_working_hours?.[weekday] || 0;
|
||||||
|
sumWorkingMinutesSickDays += hours * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
sumSickDays += days;
|
||||||
|
});
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// 9️⃣ Salden
|
||||||
|
// -------------------------------------------------------------
|
||||||
const saldo =
|
const saldo =
|
||||||
sumWorkingMinutesApproved +
|
sumWorkingMinutesApproved +
|
||||||
sumWorkingMinutesRecreationDays +
|
sumWorkingMinutesRecreationDays +
|
||||||
sumWorkingMinutesVacationDays +
|
sumWorkingMinutesVacationDays +
|
||||||
sumWorkingMinutesSickDays -
|
sumWorkingMinutesSickDays -
|
||||||
timeSpanWorkingMinutes
|
timeSpanWorkingMinutes;
|
||||||
|
|
||||||
const saldoInOfficial =
|
const saldoInOfficial =
|
||||||
sumWorkingMinutesEingereicht +
|
sumWorkingMinutesEingereicht +
|
||||||
sumWorkingMinutesRecreationDays +
|
sumWorkingMinutesRecreationDays +
|
||||||
sumWorkingMinutesVacationDays +
|
sumWorkingMinutesVacationDays +
|
||||||
sumWorkingMinutesSickDays -
|
sumWorkingMinutesSickDays -
|
||||||
timeSpanWorkingMinutes
|
timeSpanWorkingMinutes;
|
||||||
|
|
||||||
// 📦 Rückgabe (kompatibel zur alten Struktur)
|
// -------------------------------------------------------------
|
||||||
|
// 🔟 Rückgabe identisch
|
||||||
|
// -------------------------------------------------------------
|
||||||
return {
|
return {
|
||||||
user_id,
|
user_id,
|
||||||
tenant_id,
|
tenant_id,
|
||||||
@@ -154,6 +227,6 @@ export async function generateTimesEvaluation(
|
|||||||
sumSickDays,
|
sumSickDays,
|
||||||
saldo,
|
saldo,
|
||||||
saldoInOfficial,
|
saldoInOfficial,
|
||||||
times
|
times,
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,103 +1,115 @@
|
|||||||
import { FastifyInstance } from "fastify";
|
import { FastifyInstance } from "fastify"
|
||||||
import fp from "fastify-plugin";
|
import fp from "fastify-plugin"
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken"
|
||||||
import { secrets } from "../utils/secrets";
|
import { secrets } from "../utils/secrets"
|
||||||
|
|
||||||
|
import {
|
||||||
|
authUserRoles,
|
||||||
|
authRolePermissions,
|
||||||
|
} from "../../db/schema"
|
||||||
|
|
||||||
|
import { eq, and } from "drizzle-orm"
|
||||||
|
|
||||||
export default fp(async (server: FastifyInstance) => {
|
export default fp(async (server: FastifyInstance) => {
|
||||||
server.addHook("preHandler", async (req, reply) => {
|
server.addHook("preHandler", async (req, reply) => {
|
||||||
// 1️⃣ Token holen (Header oder Cookie)
|
// 1️⃣ Token aus Header oder Cookie lesen
|
||||||
const cookieToken = req.cookies?.token;
|
const cookieToken = req.cookies?.token
|
||||||
const authHeader = req.headers.authorization;
|
const authHeader = req.headers.authorization
|
||||||
const headerToken = authHeader?.startsWith("Bearer ")
|
|
||||||
? authHeader.slice(7)
|
const headerToken =
|
||||||
: null;
|
authHeader?.startsWith("Bearer ") ? authHeader.slice(7) : null
|
||||||
|
|
||||||
const token =
|
const token =
|
||||||
headerToken && headerToken.length > 10 ? headerToken : cookieToken || null;
|
headerToken && headerToken.length > 10
|
||||||
|
? headerToken
|
||||||
|
: cookieToken || null
|
||||||
|
|
||||||
/*let token = null
|
|
||||||
|
|
||||||
if(headerToken !== null && headerToken.length > 10){
|
|
||||||
token = headerToken
|
|
||||||
} else if(cookieToken ){
|
|
||||||
token = cookieToken
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return reply.code(401).send({ error: "Authentication required" });
|
return reply.code(401).send({ error: "Authentication required" })
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 2️⃣ JWT verifizieren
|
// 2️⃣ JWT verifizieren
|
||||||
const payload = jwt.verify(token, secrets.JWT_SECRET!) as {
|
const payload = jwt.verify(token, secrets.JWT_SECRET!) as {
|
||||||
user_id: string;
|
user_id: string
|
||||||
email: string;
|
email: string
|
||||||
tenant_id: number;
|
tenant_id: number | null
|
||||||
};
|
}
|
||||||
|
|
||||||
if (!payload?.user_id) {
|
if (!payload?.user_id) {
|
||||||
return reply.code(401).send({ error: "Invalid token" });
|
return reply.code(401).send({ error: "Invalid token" })
|
||||||
}
|
}
|
||||||
|
|
||||||
req.user = payload;
|
// Payload an Request hängen
|
||||||
|
req.user = payload
|
||||||
|
|
||||||
if(req.user.tenant_id) {
|
// Multi-Tenant Modus ohne ausgewählten Tenant → keine Rollenprüfung
|
||||||
// 3️⃣ Rolle des Nutzers im Tenant laden
|
if (!req.user.tenant_id) {
|
||||||
const { data: roleData, error: roleError } = await server.supabase
|
return
|
||||||
.from("auth_user_roles")
|
|
||||||
.select("role_id")
|
|
||||||
.eq("user_id", payload.user_id)
|
|
||||||
.eq("tenant_id", payload.tenant_id)
|
|
||||||
.maybeSingle();
|
|
||||||
|
|
||||||
if (roleError) {
|
|
||||||
console.log("Error fetching user role", roleError);
|
|
||||||
return reply.code(500).send({ error: "Failed to load user role" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!roleData) {
|
|
||||||
return reply.code(403).send({ error: "No role assigned for this tenant" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const roleId = roleData.role_id;
|
|
||||||
|
|
||||||
// 4️⃣ Berechtigungen der Rolle laden
|
|
||||||
const { data: permissions, error: permsError } = await server.supabase
|
|
||||||
.from("auth_role_permissions")
|
|
||||||
.select("permission")
|
|
||||||
.eq("role_id", roleId);
|
|
||||||
|
|
||||||
if (permsError) {
|
|
||||||
console.log("Failed to load permissions", permsError);
|
|
||||||
return reply.code(500).send({ error: "Permission lookup failed" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const perms = permissions?.map((p) => p.permission) ?? [];
|
|
||||||
|
|
||||||
// 5️⃣ An Request hängen
|
|
||||||
req.role = roleId;
|
|
||||||
req.permissions = perms;
|
|
||||||
req.hasPermission = (perm: string) => perms.includes(perm);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tenantId = req.user.tenant_id
|
||||||
|
const userId = req.user.user_id
|
||||||
|
|
||||||
|
// --------------------------------------------------------
|
||||||
|
// 3️⃣ Rolle des Nutzers im Tenant holen
|
||||||
|
// --------------------------------------------------------
|
||||||
|
const roleRows = await server.db
|
||||||
|
.select()
|
||||||
|
.from(authUserRoles)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(authUserRoles.user_id, userId),
|
||||||
|
eq(authUserRoles.tenant_id, tenantId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
|
||||||
|
if (roleRows.length === 0) {
|
||||||
|
return reply
|
||||||
|
.code(403)
|
||||||
|
.send({ error: "No role assigned for this tenant" })
|
||||||
|
}
|
||||||
|
|
||||||
|
const roleId = roleRows[0].role_id
|
||||||
|
|
||||||
|
// --------------------------------------------------------
|
||||||
|
// 4️⃣ Berechtigungen der Rolle laden
|
||||||
|
// --------------------------------------------------------
|
||||||
|
const permissionRows = await server.db
|
||||||
|
.select()
|
||||||
|
.from(authRolePermissions)
|
||||||
|
.where(eq(authRolePermissions.role_id, roleId))
|
||||||
|
|
||||||
|
const permissions = permissionRows.map((p) => p.permission)
|
||||||
|
|
||||||
|
// --------------------------------------------------------
|
||||||
|
// 5️⃣ An Request hängen für spätere Nutzung
|
||||||
|
// --------------------------------------------------------
|
||||||
|
req.role = roleId
|
||||||
|
req.permissions = permissions
|
||||||
|
req.hasPermission = (perm: string) => permissions.includes(perm)
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
return reply.code(401).send({ error: "Invalid or expired token" });
|
console.error("JWT verification error:", err)
|
||||||
|
return reply.code(401).send({ error: "Invalid or expired token" })
|
||||||
}
|
}
|
||||||
});
|
})
|
||||||
});
|
})
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Fastify TypeScript Erweiterungen
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
// 🧩 Fastify Type Declarations
|
|
||||||
declare module "fastify" {
|
declare module "fastify" {
|
||||||
interface FastifyRequest {
|
interface FastifyRequest {
|
||||||
user: {
|
user: {
|
||||||
user_id: string;
|
user_id: string
|
||||||
email: string;
|
email: string
|
||||||
tenant_id: number;
|
tenant_id: number | null
|
||||||
};
|
}
|
||||||
role: string;
|
role: string
|
||||||
permissions: string[];
|
permissions: string[]
|
||||||
hasPermission: (permission: string) => boolean;
|
hasPermission: (permission: string) => boolean
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
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 as { id?: string }
|
|
||||||
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
server.put("/user/:id/profile", async (req, reply) => {
|
|
||||||
|
|
||||||
const { id } = req.params as { id?: string }
|
|
||||||
|
|
||||||
const { data } = req.body as { data?: object }
|
|
||||||
|
|
||||||
// 4. Profil für den aktiven Tenant laden
|
|
||||||
let profile = null
|
|
||||||
if (req.user.tenant_id) {
|
|
||||||
const { data: profileData } = await server.supabase
|
|
||||||
.from("auth_profiles")
|
|
||||||
.select("*")
|
|
||||||
.eq("user_id", req.user.user_id)
|
|
||||||
.eq("tenant_id", req.user.tenant_id)
|
|
||||||
.single()
|
|
||||||
|
|
||||||
profile = profileData
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
//Update Profile
|
|
||||||
const { data: updatedProfileData, error: updateError } = await server.supabase
|
|
||||||
.from("auth_profiles")
|
|
||||||
.update(data)
|
|
||||||
.eq("user_id", id)
|
|
||||||
.eq("id", profile?.id)
|
|
||||||
.select("*")
|
|
||||||
.single()
|
|
||||||
|
|
||||||
console.log(updateError)
|
|
||||||
console.log(updatedProfileData)
|
|
||||||
|
|
||||||
// 5. Permissions laden (über Funktion)
|
|
||||||
|
|
||||||
// 6. Response zurückgeben
|
|
||||||
return {
|
|
||||||
data,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -1,143 +1,173 @@
|
|||||||
import { FastifyInstance } from 'fastify'
|
import { FastifyInstance } from "fastify"
|
||||||
import { StaffTimeEntry } from '../../types/staff'
|
import {
|
||||||
|
stafftimeentries,
|
||||||
|
stafftimenetryconnects
|
||||||
|
} from "../../../db/schema"
|
||||||
|
import {
|
||||||
|
eq,
|
||||||
|
and,
|
||||||
|
gte,
|
||||||
|
lte,
|
||||||
|
desc
|
||||||
|
} from "drizzle-orm"
|
||||||
|
|
||||||
export default async function staffTimeRoutes(server: FastifyInstance) {
|
export default async function staffTimeRoutes(server: FastifyInstance) {
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
// ▶ Neue Zeit starten
|
// ▶ Neue Zeit starten
|
||||||
server.post(
|
// -------------------------------------------------------------
|
||||||
'/staff/time',
|
server.post("/staff/time", async (req, reply) => {
|
||||||
async (req, reply) => {
|
try {
|
||||||
const { started_at, stopped_at, type = 'work', description, user_id } = req.body as any
|
const { user_id, ...rest } = req.body as any
|
||||||
const userId = req.user.user_id
|
const userId = req.user.user_id
|
||||||
const tenantId = req.user.tenant_id
|
const tenantId = req.user.tenant_id
|
||||||
|
|
||||||
|
const newEntry = {
|
||||||
let dataToInsert = {
|
|
||||||
tenant_id: tenantId,
|
tenant_id: tenantId,
|
||||||
user_id: user_id ? user_id : userId,
|
user_id: user_id || userId,
|
||||||
// @ts-ignore
|
...rest
|
||||||
...req.body
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
const [created] = await server.db
|
||||||
.from('staff_time_entries')
|
.insert(stafftimeentries)
|
||||||
.insert([dataToInsert])
|
.values(newEntry)
|
||||||
.select()
|
.returning()
|
||||||
.maybeSingle()
|
|
||||||
|
|
||||||
if (error) return reply.code(400).send({ error: error.message })
|
return created
|
||||||
return reply.send(data)
|
} catch (err: any) {
|
||||||
|
console.error(err)
|
||||||
|
return reply.code(400).send({ error: err.message })
|
||||||
}
|
}
|
||||||
)
|
|
||||||
|
|
||||||
// ▶ Zeit stoppen
|
|
||||||
server.put<{ Params: { id: string }, Body: { stopped_at: string } }>(
|
|
||||||
'/staff/time/:id/stop',
|
|
||||||
async (req, reply) => {
|
|
||||||
const { id } = req.params
|
|
||||||
const { stopped_at } = req.body
|
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
|
||||||
.from('staff_time_entries')
|
|
||||||
.update({ stopped_at, updated_at: new Date().toISOString() })
|
|
||||||
.eq('id', id)
|
|
||||||
.select()
|
|
||||||
.maybeSingle()
|
|
||||||
|
|
||||||
if (error) return reply.code(400).send({ error: error.message })
|
|
||||||
return reply.send(data)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ▶ Liste aller Zeiten
|
|
||||||
server.get<{
|
|
||||||
Querystring: {
|
|
||||||
from?: string
|
|
||||||
to?: string
|
|
||||||
type?: string
|
|
||||||
user_id?: string
|
|
||||||
}
|
|
||||||
}>('/staff/time', async (req, reply) => {
|
|
||||||
const { from, to, type, user_id } = req.query
|
|
||||||
const { user_id: currentUserId, tenant_id } = req.user
|
|
||||||
|
|
||||||
// 🧩 Basis-Query für den Tenant
|
|
||||||
let query = server.supabase
|
|
||||||
.from('staff_time_entries')
|
|
||||||
.select('*')
|
|
||||||
.eq('tenant_id', tenant_id)
|
|
||||||
.order('started_at', { ascending: false })
|
|
||||||
|
|
||||||
// 🔒 Zugriffsbeschränkung: nur eigene Zeiten, außer Berechtigung erlaubt mehr
|
|
||||||
if (!req.hasPermission('staff.time.read_all')) {
|
|
||||||
query = query.eq('user_id', currentUserId)
|
|
||||||
} else if (user_id) {
|
|
||||||
// falls explizit user_id angegeben wurde
|
|
||||||
query = query.eq('user_id', user_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 📅 Zeitfilter
|
|
||||||
if (from) query = query.gte('started_at', from)
|
|
||||||
if (to) query = query.lte('started_at', to)
|
|
||||||
if (type) query = query.eq('type', type)
|
|
||||||
|
|
||||||
const { data, error } = await query
|
|
||||||
if (error) return reply.code(400).send({ error: error.message })
|
|
||||||
|
|
||||||
return reply.send(data)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// ▶ Zeit stoppen
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
server.put("/staff/time/:id/stop", async (req, reply) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params as any
|
||||||
|
const { stopped_at } = req.body as any
|
||||||
|
|
||||||
// ▶ Einzelne Zeit abrufen (inkl. Connects)
|
const [updated] = await server.db
|
||||||
server.get<{ Params: { id: string } }>(
|
.update(stafftimeentries)
|
||||||
'/staff/time/:id',
|
.set({
|
||||||
async (req, reply) => {
|
stopped_at,
|
||||||
const { id } = req.params
|
updated_at: new Date()
|
||||||
|
})
|
||||||
|
.where(eq(stafftimeentries.id, id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
return updated
|
||||||
.from('staff_time_entries')
|
} catch (err) {
|
||||||
.select(`
|
return reply.code(400).send({ error: (err as Error).message })
|
||||||
*,
|
|
||||||
staff_time_entry_connects(*)
|
|
||||||
`)
|
|
||||||
.eq('id', id)
|
|
||||||
.maybeSingle()
|
|
||||||
|
|
||||||
if (error) return reply.code(400).send({ error: error.message })
|
|
||||||
return reply.send(data)
|
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
|
|
||||||
// ▶ Zeit bearbeiten
|
// -------------------------------------------------------------
|
||||||
server.put<{ Params: { id: string }, Body: Partial<StaffTimeEntry> }>(
|
// ▶ Liste aller Zeiten
|
||||||
'/staff/time/:id',
|
// -------------------------------------------------------------
|
||||||
async (req, reply) => {
|
server.get("/staff/time", async (req, reply) => {
|
||||||
const { id } = req.params
|
try {
|
||||||
|
const { from, to, type, user_id } = req.query as any
|
||||||
|
const { tenant_id, user_id: currentUserId } = req.user
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
let where = and(eq(stafftimeentries.tenant_id, tenant_id))
|
||||||
.from('staff_time_entries')
|
|
||||||
.update({ ...req.body, updated_at: new Date().toISOString() })
|
// Zugriffsbeschränkung
|
||||||
.eq('id', id)
|
if (!req.hasPermission("staff.time.read_all")) {
|
||||||
|
where = and(where, eq(stafftimeentries.user_id, currentUserId))
|
||||||
|
} else if (user_id) {
|
||||||
|
where = and(where, eq(stafftimeentries.user_id, user_id))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (from) where = and(where, gte(stafftimeentries.started_at, from))
|
||||||
|
if (to) where = and(where, lte(stafftimeentries.started_at, to))
|
||||||
|
if (type) where = and(where, eq(stafftimeentries.type, type))
|
||||||
|
|
||||||
|
const rows = await server.db
|
||||||
.select()
|
.select()
|
||||||
.maybeSingle()
|
.from(stafftimeentries)
|
||||||
|
.where(where)
|
||||||
|
.orderBy(desc(stafftimeentries.started_at))
|
||||||
|
|
||||||
if (error) return reply.code(400).send({ error: error.message })
|
return rows
|
||||||
return reply.send(data)
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
return reply.code(400).send({ error: (err as Error).message })
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// ▶ Einzelne Zeit (inkl. Connects)
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
server.get("/staff/time/:id", async (req, reply) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params as any
|
||||||
|
|
||||||
|
const rows = await server.db
|
||||||
|
.select()
|
||||||
|
.from(stafftimeentries)
|
||||||
|
.where(eq(stafftimeentries.id, id))
|
||||||
|
.limit(1)
|
||||||
|
|
||||||
|
if (!rows.length) return reply.code(404).send({ error: "Not found" })
|
||||||
|
|
||||||
|
const entry = rows[0]
|
||||||
|
|
||||||
|
const connects = await server.db
|
||||||
|
.select()
|
||||||
|
.from(stafftimenetryconnects)
|
||||||
|
.where(eq(stafftimenetryconnects.stafftimeentry, id))
|
||||||
|
|
||||||
|
return {
|
||||||
|
...entry,
|
||||||
|
staff_time_entry_connects: connects
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return reply.code(400).send({ error: (err as Error).message })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// ▶ Zeit bearbeiten
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
server.put("/staff/time/:id", async (req, reply) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params as any
|
||||||
|
|
||||||
|
|
||||||
|
const updateData = {
|
||||||
|
// @ts-ignore
|
||||||
|
...req.body,
|
||||||
|
updated_at: new Date()
|
||||||
|
}
|
||||||
|
|
||||||
|
const [updated] = await server.db
|
||||||
|
.update(stafftimeentries)
|
||||||
|
.set(updateData)
|
||||||
|
.where(eq(stafftimeentries.id, id))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
return updated
|
||||||
|
} catch (err) {
|
||||||
|
return reply.code(400).send({ error: (err as Error).message })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
// ▶ Zeit löschen
|
// ▶ Zeit löschen
|
||||||
server.delete<{ Params: { id: string } }>(
|
// -------------------------------------------------------------
|
||||||
'/staff/time/:id',
|
server.delete("/staff/time/:id", async (req, reply) => {
|
||||||
async (req, reply) => {
|
try {
|
||||||
const { id } = req.params
|
const { id } = req.params as any
|
||||||
const { error } = await server.supabase
|
|
||||||
.from('staff_time_entries')
|
|
||||||
.delete()
|
|
||||||
.eq('id', id)
|
|
||||||
|
|
||||||
if (error) return reply.code(400).send({ error: error.message })
|
await server.db
|
||||||
return reply.send({ success: true })
|
.delete(stafftimeentries)
|
||||||
|
.where(eq(stafftimeentries.id, id))
|
||||||
|
|
||||||
|
return { success: true }
|
||||||
|
} catch (err) {
|
||||||
|
return reply.code(400).send({ error: (err as Error).message })
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,43 +1,67 @@
|
|||||||
// 🔧 Hilfsfunktionen
|
// 🔧 Hilfsfunktionen
|
||||||
import {FastifyInstance} from "fastify";
|
import { FastifyInstance } from "fastify"
|
||||||
|
import { eq, ilike, and } from "drizzle-orm"
|
||||||
|
|
||||||
|
import { contacts, customers } from "../../db/schema"
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// Extract Domain
|
||||||
|
// -------------------------------------------------------------
|
||||||
export function extractDomain(email: string) {
|
export function extractDomain(email: string) {
|
||||||
if (!email) return null
|
if (!email) return null
|
||||||
const parts = email.split('@')
|
const parts = email.split("@")
|
||||||
return parts.length === 2 ? parts[1].toLowerCase() : null
|
return parts.length === 2 ? parts[1].toLowerCase() : null
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function findCustomerOrContactByEmailOrDomain(server:FastifyInstance, fromMail: string, tenantId: number) {
|
// -------------------------------------------------------------
|
||||||
|
// Kunde oder Kontakt anhand E-Mail oder Domain finden
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
export async function findCustomerOrContactByEmailOrDomain(
|
||||||
|
server: FastifyInstance,
|
||||||
|
fromMail: string,
|
||||||
|
tenantId: number
|
||||||
|
) {
|
||||||
const sender = fromMail.toLowerCase()
|
const sender = fromMail.toLowerCase()
|
||||||
const senderDomain = extractDomain(sender)
|
const senderDomain = extractDomain(sender)
|
||||||
if (!senderDomain) return null
|
if (!senderDomain) return null
|
||||||
|
|
||||||
// 1️⃣ Direkter Match über contacts
|
// 1️⃣ Direkter Match über Contacts (email)
|
||||||
const { data: contactMatch } = await server.supabase
|
const contactMatch = await server.db
|
||||||
.from('contacts')
|
.select({
|
||||||
.select('id, customer')
|
id: contacts.id,
|
||||||
.eq('email', sender)
|
customer: contacts.customer,
|
||||||
.eq('tenant', tenantId)
|
})
|
||||||
.maybeSingle()
|
.from(contacts)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(contacts.email, sender),
|
||||||
|
eq(contacts.tenant, tenantId)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
|
||||||
if (contactMatch?.customer) {
|
if (contactMatch.length && contactMatch[0].customer) {
|
||||||
return { customer: contactMatch.customer, contact: contactMatch.id }
|
return {
|
||||||
|
customer: contactMatch[0].customer,
|
||||||
|
contact: contactMatch[0].id,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2️⃣ Kunden nach Domain oder Rechnungs-E-Mail durchsuchen
|
// 2️⃣ Kunden anhand Domain vergleichen
|
||||||
const { data: customers, error } = await server.supabase
|
const allCustomers = await server.db
|
||||||
.from('customers')
|
.select({
|
||||||
.select('id, infoData')
|
id: customers.id,
|
||||||
.eq('tenant', tenantId)
|
infoData: customers.infoData,
|
||||||
|
})
|
||||||
|
.from(customers)
|
||||||
|
.where(eq(customers.tenant, tenantId))
|
||||||
|
|
||||||
if (error) {
|
for (const c of allCustomers) {
|
||||||
server.log.error(`[Helpdesk] Fehler beim Laden der Kunden: ${error.message}`)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const c of customers || []) {
|
|
||||||
const info = c.infoData || {}
|
const info = c.infoData || {}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
const email = info.email?.toLowerCase()
|
const email = info.email?.toLowerCase()
|
||||||
|
//@ts-ignore
|
||||||
const invoiceEmail = info.invoiceEmail?.toLowerCase()
|
const invoiceEmail = info.invoiceEmail?.toLowerCase()
|
||||||
const emailDomain = extractDomain(email)
|
const emailDomain = extractDomain(email)
|
||||||
const invoiceDomain = extractDomain(invoiceEmail)
|
const invoiceDomain = extractDomain(invoiceEmail)
|
||||||
@@ -55,18 +79,28 @@ export async function findCustomerOrContactByEmailOrDomain(server:FastifyInstanc
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// getNestedValue (für Sortierung & Suche im Backend)
|
||||||
|
// -------------------------------------------------------------
|
||||||
export function getNestedValue(obj: any, path: string): any {
|
export function getNestedValue(obj: any, path: string): any {
|
||||||
return path.split('.').reduce((acc, part) => acc?.[part], obj);
|
return path
|
||||||
|
.split(".")
|
||||||
|
.reduce((acc, part) => (acc && acc[part] !== undefined ? acc[part] : undefined), obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// -------------------------------------------------------------
|
||||||
|
// compareValues (Sortierung für paginated)
|
||||||
|
// -------------------------------------------------------------
|
||||||
export function compareValues(a: any, b: any): number {
|
export function compareValues(a: any, b: any): number {
|
||||||
if (a === b) return 0;
|
if (a === b) return 0
|
||||||
if (a == null) return 1;
|
if (a == null) return 1
|
||||||
if (b == null) return -1;
|
if (b == null) return -1
|
||||||
|
|
||||||
if (typeof a === 'string' && typeof b === 'string') {
|
// String Compare
|
||||||
return a.localeCompare(b);
|
if (typeof a === "string" && typeof b === "string") {
|
||||||
|
return a.localeCompare(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
return a < b ? -1 : 1;
|
// Numerisch
|
||||||
}
|
return a < b ? -1 : 1
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
|
accounts, bankaccounts, bankrequisitions, bankstatements,
|
||||||
contacts,
|
contacts,
|
||||||
contracts, costcentres, createddocuments,
|
contracts, costcentres, createddocuments,
|
||||||
customers,
|
customers,
|
||||||
files, filetags, folders, hourrates, inventoryitemgroups,
|
files, filetags, folders, hourrates, incominginvoices, inventoryitemgroups,
|
||||||
inventoryitems, letterheads, ownaccounts,
|
inventoryitems, letterheads, ownaccounts,
|
||||||
plants, productcategories, products,
|
plants, productcategories, products,
|
||||||
projects,
|
projects,
|
||||||
projecttypes, servicecategories, services, spaces, tasks, texttemplates, units, vehicles,
|
projecttypes, servicecategories, services, spaces, statementallocations, tasks, texttemplates, units, vehicles,
|
||||||
vendors
|
vendors
|
||||||
} from "../../db/schema";
|
} from "../../db/schema";
|
||||||
|
|
||||||
@@ -106,9 +107,34 @@ export const resourceConfig = {
|
|||||||
},
|
},
|
||||||
createddocuments: {
|
createddocuments: {
|
||||||
table: createddocuments,
|
table: createddocuments,
|
||||||
mtoLoad: ["customer", "project", "contact", "contract", "plant","letterhead",]
|
mtoLoad: ["customer", "project", "contact", "contract", "plant","letterhead"],
|
||||||
|
mtmLoad: ["statementallocations"],
|
||||||
|
mtmListLoad: ["statementallocations"],
|
||||||
},
|
},
|
||||||
texttemplates: {
|
texttemplates: {
|
||||||
table: texttemplates
|
table: texttemplates
|
||||||
|
},
|
||||||
|
incominginvoices: {
|
||||||
|
table: incominginvoices,
|
||||||
|
mtmLoad: ["statementallocations"],
|
||||||
|
mtmListLoad: ["statementallocations"],
|
||||||
|
},
|
||||||
|
statementallocations: {
|
||||||
|
table: statementallocations,
|
||||||
|
mtoLoad: ["customer","vendor","incominginvoice","createddocument","ownaccount","bankstatement"]
|
||||||
|
},
|
||||||
|
accounts: {
|
||||||
|
table: accounts,
|
||||||
|
},
|
||||||
|
bankstatements: {
|
||||||
|
table: bankstatements,
|
||||||
|
mtmListLoad: ["statementallocations"],
|
||||||
|
mtmLoad: ["statementallocations"],
|
||||||
|
},
|
||||||
|
bankaccounts: {
|
||||||
|
table: bankaccounts,
|
||||||
|
},
|
||||||
|
bankrequisitions: {
|
||||||
|
table: bankrequisitions,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user