Compare commits
10 Commits
c0faa398b8
...
f793d4cce6
| Author | SHA1 | Date | |
|---|---|---|---|
| f793d4cce6 | |||
| c3f46cd184 | |||
| 6bf336356d | |||
| 55699da42c | |||
| 053f184a33 | |||
| 6541cb2adf | |||
| 7dca84947e | |||
| 45fd6fda08 | |||
| 31e80fb386 | |||
| 7ea28cc6c0 |
3
backend/.secretlintrc.json
Normal file
3
backend/.secretlintrc.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"rules": []
|
||||||
|
}
|
||||||
@@ -29,7 +29,6 @@
|
|||||||
"@infisical/sdk": "^4.0.6",
|
"@infisical/sdk": "^4.0.6",
|
||||||
"@mmote/niimbluelib": "^0.0.1-alpha.29",
|
"@mmote/niimbluelib": "^0.0.1-alpha.29",
|
||||||
"@prisma/client": "^6.15.0",
|
"@prisma/client": "^6.15.0",
|
||||||
"@supabase/supabase-js": "^2.56.1",
|
|
||||||
"@zip.js/zip.js": "^2.7.73",
|
"@zip.js/zip.js": "^2.7.73",
|
||||||
"archiver": "^7.0.1",
|
"archiver": "^7.0.1",
|
||||||
"axios": "^1.12.1",
|
"axios": "^1.12.1",
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import Fastify from "fastify";
|
import Fastify from "fastify";
|
||||||
import swaggerPlugin from "./plugins/swagger"
|
import swaggerPlugin from "./plugins/swagger"
|
||||||
import supabasePlugin from "./plugins/supabase";
|
|
||||||
import dayjsPlugin from "./plugins/dayjs";
|
import dayjsPlugin from "./plugins/dayjs";
|
||||||
import healthRoutes from "./routes/health";
|
import healthRoutes from "./routes/health";
|
||||||
import meRoutes from "./routes/auth/me";
|
import meRoutes from "./routes/auth/me";
|
||||||
@@ -73,7 +72,6 @@ async function main() {
|
|||||||
|
|
||||||
// Plugins Global verfügbar
|
// Plugins Global verfügbar
|
||||||
await app.register(swaggerPlugin);
|
await app.register(swaggerPlugin);
|
||||||
await app.register(supabasePlugin);
|
|
||||||
await app.register(tenantPlugin);
|
await app.register(tenantPlugin);
|
||||||
await app.register(dayjsPlugin);
|
await app.register(dayjsPlugin);
|
||||||
await app.register(dbPlugin);
|
await app.register(dbPlugin);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// modules/helpdesk/helpdesk.contact.service.ts
|
// modules/helpdesk/helpdesk.contact.service.ts
|
||||||
import { FastifyInstance } from 'fastify'
|
import { FastifyInstance } from 'fastify'
|
||||||
|
import { and, eq, or } from "drizzle-orm";
|
||||||
|
import { helpdesk_contacts } from "../../../db/schema";
|
||||||
|
|
||||||
export async function getOrCreateContact(
|
export async function getOrCreateContact(
|
||||||
server: FastifyInstance,
|
server: FastifyInstance,
|
||||||
@@ -9,30 +11,35 @@ export async function getOrCreateContact(
|
|||||||
if (!email && !phone) throw new Error('Contact must have at least an email or phone')
|
if (!email && !phone) throw new Error('Contact must have at least an email or phone')
|
||||||
|
|
||||||
// Bestehenden Kontakt prüfen
|
// Bestehenden Kontakt prüfen
|
||||||
const { data: existing, error: findError } = await server.supabase
|
const matchConditions = []
|
||||||
.from('helpdesk_contacts')
|
if (email) matchConditions.push(eq(helpdesk_contacts.email, email))
|
||||||
.select('*')
|
if (phone) matchConditions.push(eq(helpdesk_contacts.phone, phone))
|
||||||
.eq('tenant_id', tenant_id)
|
|
||||||
.or(`email.eq.${email || ''},phone.eq.${phone || ''}`)
|
|
||||||
.maybeSingle()
|
|
||||||
|
|
||||||
if (findError) throw findError
|
const existing = await server.db
|
||||||
if (existing) return existing
|
.select()
|
||||||
|
.from(helpdesk_contacts)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(helpdesk_contacts.tenantId, tenant_id),
|
||||||
|
or(...matchConditions)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.limit(1)
|
||||||
|
|
||||||
|
if (existing[0]) return existing[0]
|
||||||
|
|
||||||
// Anlegen
|
// Anlegen
|
||||||
const { data: created, error: insertError } = await server.supabase
|
const created = await server.db
|
||||||
.from('helpdesk_contacts')
|
.insert(helpdesk_contacts)
|
||||||
.insert({
|
.values({
|
||||||
tenant_id,
|
tenantId: tenant_id,
|
||||||
email,
|
email,
|
||||||
phone,
|
phone,
|
||||||
display_name,
|
displayName: display_name,
|
||||||
customer_id,
|
customerId: customer_id,
|
||||||
contact_id
|
contactId: contact_id
|
||||||
})
|
})
|
||||||
.select()
|
.returning()
|
||||||
.single()
|
|
||||||
|
|
||||||
if (insertError) throw insertError
|
return created[0]
|
||||||
return created
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@
|
|||||||
import { FastifyInstance } from 'fastify'
|
import { FastifyInstance } from 'fastify'
|
||||||
import { getOrCreateContact } from './helpdesk.contact.service.js'
|
import { getOrCreateContact } from './helpdesk.contact.service.js'
|
||||||
import {useNextNumberRangeNumber} from "../../utils/functions";
|
import {useNextNumberRangeNumber} from "../../utils/functions";
|
||||||
|
import { and, desc, eq } from "drizzle-orm";
|
||||||
|
import { customers, helpdesk_contacts, helpdesk_conversations } from "../../../db/schema";
|
||||||
|
|
||||||
export async function createConversation(
|
export async function createConversation(
|
||||||
server: FastifyInstance,
|
server: FastifyInstance,
|
||||||
@@ -25,24 +27,34 @@ export async function createConversation(
|
|||||||
|
|
||||||
const {usedNumber } = await useNextNumberRangeNumber(server, tenant_id, "tickets")
|
const {usedNumber } = await useNextNumberRangeNumber(server, tenant_id, "tickets")
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
const inserted = await server.db
|
||||||
.from('helpdesk_conversations')
|
.insert(helpdesk_conversations)
|
||||||
.insert({
|
.values({
|
||||||
tenant_id,
|
tenantId: tenant_id,
|
||||||
contact_id: contactRecord.id,
|
contactId: contactRecord.id,
|
||||||
channel_instance_id,
|
channelInstanceId: channel_instance_id,
|
||||||
subject: subject || null,
|
subject: subject || null,
|
||||||
status: 'open',
|
status: 'open',
|
||||||
created_at: new Date().toISOString(),
|
createdAt: new Date(),
|
||||||
customer_id,
|
customerId: customer_id,
|
||||||
contact_person_id,
|
contactPersonId: contact_person_id,
|
||||||
ticket_number: usedNumber
|
ticketNumber: usedNumber
|
||||||
})
|
})
|
||||||
.select()
|
.returning()
|
||||||
.single()
|
|
||||||
|
|
||||||
if (error) throw error
|
const data = inserted[0]
|
||||||
return data
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
channel_instance_id: data.channelInstanceId,
|
||||||
|
contact_id: data.contactId,
|
||||||
|
contact_person_id: data.contactPersonId,
|
||||||
|
created_at: data.createdAt,
|
||||||
|
customer_id: data.customerId,
|
||||||
|
last_message_at: data.lastMessageAt,
|
||||||
|
tenant_id: data.tenantId,
|
||||||
|
ticket_number: data.ticketNumber,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getConversations(
|
export async function getConversations(
|
||||||
@@ -52,22 +64,34 @@ export async function getConversations(
|
|||||||
) {
|
) {
|
||||||
const { status, limit = 50 } = opts || {}
|
const { status, limit = 50 } = opts || {}
|
||||||
|
|
||||||
let query = server.supabase.from('helpdesk_conversations').select('*, customer_id(*)').eq('tenant_id', tenant_id)
|
const filters = [eq(helpdesk_conversations.tenantId, tenant_id)]
|
||||||
|
if (status) filters.push(eq(helpdesk_conversations.status, status))
|
||||||
|
|
||||||
if (status) query = query.eq('status', status)
|
const data = await server.db
|
||||||
query = query.order('last_message_at', { ascending: false }).limit(limit)
|
.select({
|
||||||
|
conversation: helpdesk_conversations,
|
||||||
|
contact: helpdesk_contacts,
|
||||||
|
customer: customers,
|
||||||
|
})
|
||||||
|
.from(helpdesk_conversations)
|
||||||
|
.leftJoin(helpdesk_contacts, eq(helpdesk_contacts.id, helpdesk_conversations.contactId))
|
||||||
|
.leftJoin(customers, eq(customers.id, helpdesk_conversations.customerId))
|
||||||
|
.where(and(...filters))
|
||||||
|
.orderBy(desc(helpdesk_conversations.lastMessageAt))
|
||||||
|
.limit(limit)
|
||||||
|
|
||||||
const { data, error } = await query
|
return data.map((entry) => ({
|
||||||
if (error) throw error
|
...entry.conversation,
|
||||||
|
helpdesk_contacts: entry.contact,
|
||||||
const mappedData = data.map(entry => {
|
channel_instance_id: entry.conversation.channelInstanceId,
|
||||||
return {
|
contact_id: entry.conversation.contactId,
|
||||||
...entry,
|
contact_person_id: entry.conversation.contactPersonId,
|
||||||
customer: entry.customer_id
|
created_at: entry.conversation.createdAt,
|
||||||
}
|
customer_id: entry.customer,
|
||||||
})
|
last_message_at: entry.conversation.lastMessageAt,
|
||||||
|
tenant_id: entry.conversation.tenantId,
|
||||||
return mappedData
|
ticket_number: entry.conversation.ticketNumber,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateConversationStatus(
|
export async function updateConversationStatus(
|
||||||
@@ -78,13 +102,22 @@ export async function updateConversationStatus(
|
|||||||
const valid = ['open', 'in_progress', 'waiting_for_customer', 'answered', 'closed']
|
const valid = ['open', 'in_progress', 'waiting_for_customer', 'answered', 'closed']
|
||||||
if (!valid.includes(status)) throw new Error('Invalid status')
|
if (!valid.includes(status)) throw new Error('Invalid status')
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
const updated = await server.db
|
||||||
.from('helpdesk_conversations')
|
.update(helpdesk_conversations)
|
||||||
.update({ status })
|
.set({ status })
|
||||||
.eq('id', conversation_id)
|
.where(eq(helpdesk_conversations.id, conversation_id))
|
||||||
.select()
|
.returning()
|
||||||
.single()
|
|
||||||
|
|
||||||
if (error) throw error
|
const data = updated[0]
|
||||||
return data
|
return {
|
||||||
|
...data,
|
||||||
|
channel_instance_id: data.channelInstanceId,
|
||||||
|
contact_id: data.contactId,
|
||||||
|
contact_person_id: data.contactPersonId,
|
||||||
|
created_at: data.createdAt,
|
||||||
|
customer_id: data.customerId,
|
||||||
|
last_message_at: data.lastMessageAt,
|
||||||
|
tenant_id: data.tenantId,
|
||||||
|
ticket_number: data.ticketNumber,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
// modules/helpdesk/helpdesk.message.service.ts
|
// modules/helpdesk/helpdesk.message.service.ts
|
||||||
import { FastifyInstance } from 'fastify'
|
import { FastifyInstance } from 'fastify'
|
||||||
|
import { asc, eq } from "drizzle-orm";
|
||||||
|
import { helpdesk_conversations, helpdesk_messages } from "../../../db/schema";
|
||||||
|
|
||||||
export async function addMessage(
|
export async function addMessage(
|
||||||
server: FastifyInstance,
|
server: FastifyInstance,
|
||||||
@@ -23,38 +25,53 @@ export async function addMessage(
|
|||||||
) {
|
) {
|
||||||
if (!payload?.text) throw new Error('Message payload requires text content')
|
if (!payload?.text) throw new Error('Message payload requires text content')
|
||||||
|
|
||||||
const { data: message, error } = await server.supabase
|
const inserted = await server.db
|
||||||
.from('helpdesk_messages')
|
.insert(helpdesk_messages)
|
||||||
.insert({
|
.values({
|
||||||
tenant_id,
|
tenantId: tenant_id,
|
||||||
conversation_id,
|
conversationId: conversation_id,
|
||||||
author_user_id,
|
authorUserId: author_user_id,
|
||||||
direction,
|
direction,
|
||||||
payload,
|
payload,
|
||||||
raw_meta,
|
rawMeta: raw_meta,
|
||||||
created_at: new Date().toISOString(),
|
externalMessageId: external_message_id,
|
||||||
|
receivedAt: new Date(),
|
||||||
})
|
})
|
||||||
.select()
|
.returning()
|
||||||
.single()
|
|
||||||
|
|
||||||
if (error) throw error
|
const message = inserted[0]
|
||||||
|
|
||||||
// Letzte Nachricht aktualisieren
|
// Letzte Nachricht aktualisieren
|
||||||
await server.supabase
|
await server.db
|
||||||
.from('helpdesk_conversations')
|
.update(helpdesk_conversations)
|
||||||
.update({ last_message_at: new Date().toISOString() })
|
.set({ lastMessageAt: new Date() })
|
||||||
.eq('id', conversation_id)
|
.where(eq(helpdesk_conversations.id, conversation_id))
|
||||||
|
|
||||||
return message
|
return {
|
||||||
|
...message,
|
||||||
|
author_user_id: message.authorUserId,
|
||||||
|
conversation_id: message.conversationId,
|
||||||
|
created_at: message.createdAt,
|
||||||
|
external_message_id: message.externalMessageId,
|
||||||
|
raw_meta: message.rawMeta,
|
||||||
|
tenant_id: message.tenantId,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMessages(server: FastifyInstance, conversation_id: string) {
|
export async function getMessages(server: FastifyInstance, conversation_id: string) {
|
||||||
const { data, error } = await server.supabase
|
const data = await server.db
|
||||||
.from('helpdesk_messages')
|
.select()
|
||||||
.select('*')
|
.from(helpdesk_messages)
|
||||||
.eq('conversation_id', conversation_id)
|
.where(eq(helpdesk_messages.conversationId, conversation_id))
|
||||||
.order('created_at', { ascending: true })
|
.orderBy(asc(helpdesk_messages.createdAt))
|
||||||
|
|
||||||
if (error) throw error
|
return data.map((message) => ({
|
||||||
return data
|
...message,
|
||||||
|
author_user_id: message.authorUserId,
|
||||||
|
conversation_id: message.conversationId,
|
||||||
|
created_at: message.createdAt,
|
||||||
|
external_message_id: message.externalMessageId,
|
||||||
|
raw_meta: message.rawMeta,
|
||||||
|
tenant_id: message.tenantId,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
// services/notification.service.ts
|
// services/notification.service.ts
|
||||||
import type { FastifyInstance } from 'fastify';
|
import type { FastifyInstance } from 'fastify';
|
||||||
import {secrets} from "../utils/secrets";
|
import {secrets} from "../utils/secrets";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { notificationsEventTypes, notificationsItems } from "../../db/schema";
|
||||||
|
|
||||||
export type NotificationStatus = 'queued' | 'sent' | 'failed';
|
export type NotificationStatus = 'queued' | 'sent' | 'failed';
|
||||||
|
|
||||||
@@ -34,16 +36,16 @@ export class NotificationService {
|
|||||||
*/
|
*/
|
||||||
async trigger(input: TriggerInput) {
|
async trigger(input: TriggerInput) {
|
||||||
const { tenantId, userId, eventType, title, message, payload } = input;
|
const { tenantId, userId, eventType, title, message, payload } = input;
|
||||||
const supabase = this.server.supabase;
|
|
||||||
|
|
||||||
// 1) Event-Typ prüfen (aktiv?)
|
// 1) Event-Typ prüfen (aktiv?)
|
||||||
const { data: eventTypeRow, error: etErr } = await supabase
|
const eventTypeRows = await this.server.db
|
||||||
.from('notifications_event_types')
|
.select()
|
||||||
.select('event_key,is_active')
|
.from(notificationsEventTypes)
|
||||||
.eq('event_key', eventType)
|
.where(eq(notificationsEventTypes.eventKey, eventType))
|
||||||
.maybeSingle();
|
.limit(1)
|
||||||
|
const eventTypeRow = eventTypeRows[0]
|
||||||
|
|
||||||
if (etErr || !eventTypeRow || eventTypeRow.is_active !== true) {
|
if (!eventTypeRow || eventTypeRow.isActive !== true) {
|
||||||
throw new Error(`Unbekannter oder inaktiver Event-Typ: ${eventType}`);
|
throw new Error(`Unbekannter oder inaktiver Event-Typ: ${eventType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,40 +56,40 @@ export class NotificationService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3) Notification anlegen (status: queued)
|
// 3) Notification anlegen (status: queued)
|
||||||
const { data: inserted, error: insErr } = await supabase
|
const insertedRows = await this.server.db
|
||||||
.from('notifications_items')
|
.insert(notificationsItems)
|
||||||
.insert({
|
.values({
|
||||||
tenant_id: tenantId,
|
tenantId,
|
||||||
user_id: userId,
|
userId,
|
||||||
event_type: eventType,
|
eventType,
|
||||||
title,
|
title,
|
||||||
message,
|
message,
|
||||||
payload: payload ?? null,
|
payload: payload ?? null,
|
||||||
channel: 'email',
|
channel: 'email',
|
||||||
status: 'queued'
|
status: 'queued'
|
||||||
})
|
})
|
||||||
.select('id')
|
.returning({ id: notificationsItems.id })
|
||||||
.single();
|
const inserted = insertedRows[0]
|
||||||
|
|
||||||
if (insErr || !inserted) {
|
if (!inserted) {
|
||||||
throw new Error(`Fehler beim Einfügen der Notification: ${insErr?.message}`);
|
throw new Error("Fehler beim Einfügen der Notification");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4) E-Mail versenden
|
// 4) E-Mail versenden
|
||||||
try {
|
try {
|
||||||
await this.sendEmail(user.email, title, message);
|
await this.sendEmail(user.email, title, message);
|
||||||
|
|
||||||
await supabase
|
await this.server.db
|
||||||
.from('notifications_items')
|
.update(notificationsItems)
|
||||||
.update({ status: 'sent', sent_at: new Date().toISOString() })
|
.set({ status: 'sent', sentAt: new Date() })
|
||||||
.eq('id', inserted.id);
|
.where(eq(notificationsItems.id, inserted.id));
|
||||||
|
|
||||||
return { success: true, id: inserted.id };
|
return { success: true, id: inserted.id };
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
await supabase
|
await this.server.db
|
||||||
.from('notifications_items')
|
.update(notificationsItems)
|
||||||
.update({ status: 'failed', error: String(err?.message || err) })
|
.set({ status: 'failed', error: String(err?.message || err) })
|
||||||
.eq('id', inserted.id);
|
.where(eq(notificationsItems.id, inserted.id));
|
||||||
|
|
||||||
this.server.log.error({ err, notificationId: inserted.id }, 'E-Mail Versand fehlgeschlagen');
|
this.server.log.error({ err, notificationId: inserted.id }, 'E-Mail Versand fehlgeschlagen');
|
||||||
return { success: false, error: err?.message || 'E-Mail Versand fehlgeschlagen' };
|
return { success: false, error: err?.message || 'E-Mail Versand fehlgeschlagen' };
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
import { FastifyInstance } from "fastify";
|
|
||||||
import fp from "fastify-plugin";
|
|
||||||
import { createClient, SupabaseClient } from "@supabase/supabase-js";
|
|
||||||
import {secrets} from "../utils/secrets";
|
|
||||||
|
|
||||||
export default fp(async (server: FastifyInstance) => {
|
|
||||||
const supabaseUrl = secrets.SUPABASE_URL
|
|
||||||
const supabaseServiceKey = secrets.SUPABASE_SERVICE_ROLE_KEY
|
|
||||||
const supabase: SupabaseClient = createClient(supabaseUrl, supabaseServiceKey);
|
|
||||||
|
|
||||||
// Fastify um supabase erweitern
|
|
||||||
server.decorate("supabase", supabase);
|
|
||||||
});
|
|
||||||
|
|
||||||
declare module "fastify" {
|
|
||||||
interface FastifyInstance {
|
|
||||||
supabase: SupabaseClient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
import { FastifyInstance, FastifyRequest } from "fastify";
|
import { FastifyInstance, FastifyRequest } from "fastify";
|
||||||
import fp from "fastify-plugin";
|
import fp from "fastify-plugin";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { tenants } from "../../db/schema";
|
||||||
|
|
||||||
export default fp(async (server: FastifyInstance) => {
|
export default fp(async (server: FastifyInstance) => {
|
||||||
server.addHook("preHandler", async (req, reply) => {
|
server.addHook("preHandler", async (req, reply) => {
|
||||||
@@ -9,11 +11,12 @@ export default fp(async (server: FastifyInstance) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Tenant aus DB laden
|
// Tenant aus DB laden
|
||||||
const { data: tenant } = await server.supabase
|
const rows = await server.db
|
||||||
.from("tenants")
|
.select()
|
||||||
.select("*")
|
.from(tenants)
|
||||||
.eq("portalDomain", host)
|
.where(eq(tenants.portalDomain, host))
|
||||||
.single();
|
.limit(1);
|
||||||
|
const tenant = rows[0];
|
||||||
|
|
||||||
|
|
||||||
if(!tenant) {
|
if(!tenant) {
|
||||||
@@ -38,4 +41,4 @@ declare module "fastify" {
|
|||||||
settings?: Record<string, any>;
|
settings?: Record<string, any>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
import { FastifyInstance } from "fastify";
|
import { FastifyInstance } from "fastify";
|
||||||
import jwt from "jsonwebtoken";
|
|
||||||
import {insertHistoryItem} from "../utils/history";
|
|
||||||
import {buildExportZip} from "../utils/export/datev";
|
import {buildExportZip} from "../utils/export/datev";
|
||||||
import {s3} from "../utils/s3";
|
import {s3} from "../utils/s3";
|
||||||
import {GetObjectCommand, PutObjectCommand} from "@aws-sdk/client-s3"
|
import {GetObjectCommand, PutObjectCommand} from "@aws-sdk/client-s3"
|
||||||
@@ -9,6 +7,8 @@ import dayjs from "dayjs";
|
|||||||
import {randomUUID} from "node:crypto";
|
import {randomUUID} from "node:crypto";
|
||||||
import {secrets} from "../utils/secrets";
|
import {secrets} from "../utils/secrets";
|
||||||
import {createSEPAExport} from "../utils/export/sepa";
|
import {createSEPAExport} from "../utils/export/sepa";
|
||||||
|
import {generatedexports} from "../../db/schema";
|
||||||
|
import {eq} from "drizzle-orm";
|
||||||
|
|
||||||
const createDatevExport = async (server:FastifyInstance,req:any,startDate,endDate,beraternr,mandantennr) => {
|
const createDatevExport = async (server:FastifyInstance,req:any,startDate,endDate,beraternr,mandantennr) => {
|
||||||
try {
|
try {
|
||||||
@@ -45,25 +45,21 @@ const createDatevExport = async (server:FastifyInstance,req:any,startDate,endDat
|
|||||||
|
|
||||||
console.log(url)
|
console.log(url)
|
||||||
|
|
||||||
// 5) In Supabase-DB speichern
|
// 5) In Haupt-DB speichern
|
||||||
const { data, error } = await server.supabase
|
const inserted = await server.db
|
||||||
.from("exports")
|
.insert(generatedexports)
|
||||||
.insert([
|
.values({
|
||||||
{
|
tenantId: req.user.tenant_id,
|
||||||
tenant_id: req.user.tenant_id,
|
startDate: new Date(startDate),
|
||||||
start_date: startDate,
|
endDate: new Date(endDate),
|
||||||
end_date: endDate,
|
validUntil: dayjs().add(24, "hours").toDate(),
|
||||||
valid_until: dayjs().add(24,"hours").toISOString(),
|
filePath: fileKey,
|
||||||
file_path: fileKey,
|
url,
|
||||||
url: url,
|
type: "datev",
|
||||||
created_at: new Date().toISOString(),
|
})
|
||||||
},
|
.returning()
|
||||||
])
|
|
||||||
.select()
|
|
||||||
.single()
|
|
||||||
|
|
||||||
console.log(data)
|
console.log(inserted[0])
|
||||||
console.log(error)
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error)
|
||||||
}
|
}
|
||||||
@@ -120,9 +116,22 @@ export default async function exportRoutes(server: FastifyInstance) {
|
|||||||
//List Exports Available for Download
|
//List Exports Available for Download
|
||||||
|
|
||||||
server.get("/exports", async (req,reply) => {
|
server.get("/exports", async (req,reply) => {
|
||||||
const {data,error} = await server.supabase.from("exports").select().eq("tenant_id",req.user.tenant_id)
|
const data = await server.db
|
||||||
|
.select({
|
||||||
|
id: generatedexports.id,
|
||||||
|
created_at: generatedexports.createdAt,
|
||||||
|
tenant_id: generatedexports.tenantId,
|
||||||
|
start_date: generatedexports.startDate,
|
||||||
|
end_date: generatedexports.endDate,
|
||||||
|
valid_until: generatedexports.validUntil,
|
||||||
|
type: generatedexports.type,
|
||||||
|
url: generatedexports.url,
|
||||||
|
file_path: generatedexports.filePath,
|
||||||
|
})
|
||||||
|
.from(generatedexports)
|
||||||
|
.where(eq(generatedexports.tenantId, req.user.tenant_id))
|
||||||
|
|
||||||
console.log(data,error)
|
console.log(data)
|
||||||
reply.send(data)
|
reply.send(data)
|
||||||
|
|
||||||
})
|
})
|
||||||
@@ -131,4 +140,4 @@ export default async function exportRoutes(server: FastifyInstance) {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,17 +110,6 @@ export default async function functionRoutes(server: FastifyInstance) {
|
|||||||
const data = await server.db.select().from(citys).where(eq(citys.zip,zip))
|
const data = await server.db.select().from(citys).where(eq(citys.zip,zip))
|
||||||
|
|
||||||
|
|
||||||
/*const { data, error } = await server.supabase
|
|
||||||
.from('citys')
|
|
||||||
.select()
|
|
||||||
.eq('zip', zip)
|
|
||||||
.maybeSingle()
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.log(error)
|
|
||||||
return reply.code(500).send({ error: 'Database error' })
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if (!data) {
|
if (!data) {
|
||||||
return reply.code(404).send({ error: 'ZIP not found' })
|
return reply.code(404).send({ error: 'ZIP not found' })
|
||||||
}
|
}
|
||||||
@@ -224,4 +213,4 @@ export default async function functionRoutes(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
})*/
|
})*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ import { FastifyInstance } from "fastify";
|
|||||||
export default async function routes(server: FastifyInstance) {
|
export default async function routes(server: FastifyInstance) {
|
||||||
server.get("/ping", async () => {
|
server.get("/ping", async () => {
|
||||||
// Testquery gegen DB
|
// Testquery gegen DB
|
||||||
const { data, error } = await server.supabase.from("tenants").select("id").limit(1);
|
const result = await server.db.execute("SELECT NOW()");
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "ok",
|
status: "ok",
|
||||||
db: error ? "not connected" : "connected",
|
db: JSON.stringify(result.rows[0]),
|
||||||
tenant_count: data?.length ?? 0
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,9 @@ import { FastifyPluginAsync } from 'fastify'
|
|||||||
import { createConversation } from '../modules/helpdesk/helpdesk.conversation.service.js'
|
import { createConversation } from '../modules/helpdesk/helpdesk.conversation.service.js'
|
||||||
import { addMessage } from '../modules/helpdesk/helpdesk.message.service.js'
|
import { addMessage } from '../modules/helpdesk/helpdesk.message.service.js'
|
||||||
import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js'
|
import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js'
|
||||||
import {extractDomain, findCustomerOrContactByEmailOrDomain} from "../utils/helpers";
|
import { findCustomerOrContactByEmailOrDomain } from "../utils/helpers";
|
||||||
import {useNextNumberRangeNumber} from "../utils/functions";
|
import { eq } from "drizzle-orm";
|
||||||
|
import { helpdesk_conversations, helpdesk_messages } from "../../db/schema";
|
||||||
|
|
||||||
// -------------------------------------------------------------
|
// -------------------------------------------------------------
|
||||||
// 📧 Interne M2M-Route für eingehende E-Mails
|
// 📧 Interne M2M-Route für eingehende E-Mails
|
||||||
@@ -52,12 +53,12 @@ const helpdeskInboundEmailRoutes: FastifyPluginAsync = async (server) => {
|
|||||||
// 3️⃣ Konversation anhand In-Reply-To suchen
|
// 3️⃣ Konversation anhand In-Reply-To suchen
|
||||||
let conversationId: string | null = null
|
let conversationId: string | null = null
|
||||||
if (in_reply_to) {
|
if (in_reply_to) {
|
||||||
const { data: msg } = await server.supabase
|
const msg = await server.db
|
||||||
.from('helpdesk_messages')
|
.select({ conversationId: helpdesk_messages.conversationId })
|
||||||
.select('conversation_id')
|
.from(helpdesk_messages)
|
||||||
.eq('external_message_id', in_reply_to)
|
.where(eq(helpdesk_messages.externalMessageId, in_reply_to))
|
||||||
.maybeSingle()
|
.limit(1)
|
||||||
conversationId = msg?.conversation_id || null
|
conversationId = msg[0]?.conversationId || null
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4️⃣ Neue Konversation anlegen falls keine existiert
|
// 4️⃣ Neue Konversation anlegen falls keine existiert
|
||||||
@@ -73,12 +74,12 @@ const helpdeskInboundEmailRoutes: FastifyPluginAsync = async (server) => {
|
|||||||
})
|
})
|
||||||
conversationId = conversation.id
|
conversationId = conversation.id
|
||||||
} else {
|
} else {
|
||||||
const { data } = await server.supabase
|
const rows = await server.db
|
||||||
.from('helpdesk_conversations')
|
.select()
|
||||||
.select('*')
|
.from(helpdesk_conversations)
|
||||||
.eq('id', conversationId)
|
.where(eq(helpdesk_conversations.id, conversationId))
|
||||||
.single()
|
.limit(1)
|
||||||
conversation = data
|
conversation = rows[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5️⃣ Nachricht speichern
|
// 5️⃣ Nachricht speichern
|
||||||
@@ -96,7 +97,7 @@ const helpdeskInboundEmailRoutes: FastifyPluginAsync = async (server) => {
|
|||||||
return res.status(201).send({
|
return res.status(201).send({
|
||||||
success: true,
|
success: true,
|
||||||
conversation_id: conversationId,
|
conversation_id: conversationId,
|
||||||
ticket_number: conversation.ticket_number,
|
ticket_number: conversation?.ticket_number || conversation?.ticketNumber,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,70 +3,9 @@ import { FastifyPluginAsync } from 'fastify'
|
|||||||
import { createConversation } from '../modules/helpdesk/helpdesk.conversation.service.js'
|
import { createConversation } from '../modules/helpdesk/helpdesk.conversation.service.js'
|
||||||
import { addMessage } from '../modules/helpdesk/helpdesk.message.service.js'
|
import { addMessage } from '../modules/helpdesk/helpdesk.message.service.js'
|
||||||
import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js'
|
import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js'
|
||||||
|
import { findCustomerOrContactByEmailOrDomain } from "../utils/helpers";
|
||||||
/**
|
import { eq } from "drizzle-orm";
|
||||||
* Öffentliche Route zum Empfang eingehender Kontaktformular-Nachrichten.
|
import { helpdesk_channel_instances } from "../../db/schema";
|
||||||
* Authentifizierung: über `public_token` aus helpdesk_channel_instances
|
|
||||||
*/
|
|
||||||
|
|
||||||
function extractDomain(email) {
|
|
||||||
if (!email) return null
|
|
||||||
const parts = email.split("@")
|
|
||||||
return parts.length === 2 ? parts[1].toLowerCase() : null
|
|
||||||
}
|
|
||||||
|
|
||||||
async function findCustomerOrContactByEmailOrDomain(server,fromMail, tenantId) {
|
|
||||||
const sender = fromMail
|
|
||||||
const senderDomain = extractDomain(sender)
|
|
||||||
if (!senderDomain) return null
|
|
||||||
|
|
||||||
|
|
||||||
// 1️⃣ Direkter Match über contacts
|
|
||||||
const { data: contactMatch } = await server.supabase
|
|
||||||
.from("contacts")
|
|
||||||
.select("id, customer")
|
|
||||||
.eq("email", sender)
|
|
||||||
.eq("tenant", tenantId)
|
|
||||||
.maybeSingle()
|
|
||||||
|
|
||||||
if (contactMatch?.customer_id) return {
|
|
||||||
customer: contactMatch.customer,
|
|
||||||
contact: contactMatch.id
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2️⃣ Kunden laden, bei denen E-Mail oder Rechnungsmail passt
|
|
||||||
const { data: customers, error } = await server.supabase
|
|
||||||
.from("customers")
|
|
||||||
.select("id, infoData")
|
|
||||||
.eq("tenant", tenantId)
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
console.error(`[Helpdesk] Fehler beim Laden der Kunden:`, error.message)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3️⃣ Durch Kunden iterieren und prüfen
|
|
||||||
for (const c of customers || []) {
|
|
||||||
const info = c.infoData || {}
|
|
||||||
const email = info.email?.toLowerCase()
|
|
||||||
const invoiceEmail = info.invoiceEmail?.toLowerCase()
|
|
||||||
|
|
||||||
const emailDomain = extractDomain(email)
|
|
||||||
const invoiceDomain = extractDomain(invoiceEmail)
|
|
||||||
|
|
||||||
// exakter Match oder Domain-Match
|
|
||||||
if (
|
|
||||||
sender === email ||
|
|
||||||
sender === invoiceEmail ||
|
|
||||||
senderDomain === emailDomain ||
|
|
||||||
senderDomain === invoiceDomain
|
|
||||||
) {
|
|
||||||
return {customer: c.id, contact:null}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const helpdeskInboundRoutes: FastifyPluginAsync = async (server) => {
|
const helpdeskInboundRoutes: FastifyPluginAsync = async (server) => {
|
||||||
// Öffentliche POST-Route
|
// Öffentliche POST-Route
|
||||||
@@ -85,17 +24,18 @@ const helpdeskInboundRoutes: FastifyPluginAsync = async (server) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1️⃣ Kanalinstanz anhand des Tokens ermitteln
|
// 1️⃣ Kanalinstanz anhand des Tokens ermitteln
|
||||||
const { data: channel, error: channelError } = await server.supabase
|
const channels = await server.db
|
||||||
.from('helpdesk_channel_instances')
|
.select()
|
||||||
.select('*')
|
.from(helpdesk_channel_instances)
|
||||||
.eq('public_token', public_token)
|
.where(eq(helpdesk_channel_instances.publicToken, public_token))
|
||||||
.single()
|
.limit(1)
|
||||||
|
const channel = channels[0]
|
||||||
|
|
||||||
if (channelError || !channel) {
|
if (!channel) {
|
||||||
return res.status(404).send({ error: 'Invalid channel token' })
|
return res.status(404).send({ error: 'Invalid channel token' })
|
||||||
}
|
}
|
||||||
|
|
||||||
const tenant_id = channel.tenant_id
|
const tenant_id = channel.tenantId
|
||||||
const channel_instance_id = channel.id
|
const channel_instance_id = channel.id
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ import { addMessage, getMessages } from '../modules/helpdesk/helpdesk.message.se
|
|||||||
import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js'
|
import { getOrCreateContact } from '../modules/helpdesk/helpdesk.contact.service.js'
|
||||||
import {decrypt, encrypt} from "../utils/crypt";
|
import {decrypt, encrypt} from "../utils/crypt";
|
||||||
import nodemailer from "nodemailer"
|
import nodemailer from "nodemailer"
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import {
|
||||||
|
helpdesk_channel_instances,
|
||||||
|
helpdesk_contacts,
|
||||||
|
helpdesk_conversations,
|
||||||
|
helpdesk_messages,
|
||||||
|
} from "../../db/schema";
|
||||||
|
|
||||||
const helpdeskRoutes: FastifyPluginAsync = async (server) => {
|
const helpdeskRoutes: FastifyPluginAsync = async (server) => {
|
||||||
// 📩 1. Liste aller Konversationen
|
// 📩 1. Liste aller Konversationen
|
||||||
@@ -58,15 +65,30 @@ const helpdeskRoutes: FastifyPluginAsync = async (server) => {
|
|||||||
const tenant_id = req.user?.tenant_id
|
const tenant_id = req.user?.tenant_id
|
||||||
const {id: conversation_id} = req.params as {id: string}
|
const {id: conversation_id} = req.params as {id: string}
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
const rows = await server.db
|
||||||
.from('helpdesk_conversations')
|
.select({
|
||||||
.select('*, helpdesk_contacts(*)')
|
conversation: helpdesk_conversations,
|
||||||
.eq('tenant_id', tenant_id)
|
contact: helpdesk_contacts
|
||||||
.eq('id', conversation_id)
|
})
|
||||||
.single()
|
.from(helpdesk_conversations)
|
||||||
|
.leftJoin(helpdesk_contacts, eq(helpdesk_contacts.id, helpdesk_conversations.contactId))
|
||||||
|
.where(eq(helpdesk_conversations.id, conversation_id))
|
||||||
|
|
||||||
if (error) return res.status(404).send({ error: 'Conversation not found' })
|
const data = rows[0]
|
||||||
return res.send(data)
|
if (!data || data.conversation.tenantId !== tenant_id) return res.status(404).send({ error: 'Conversation not found' })
|
||||||
|
|
||||||
|
return res.send({
|
||||||
|
...data.conversation,
|
||||||
|
channel_instance_id: data.conversation.channelInstanceId,
|
||||||
|
contact_id: data.conversation.contactId,
|
||||||
|
contact_person_id: data.conversation.contactPersonId,
|
||||||
|
created_at: data.conversation.createdAt,
|
||||||
|
customer_id: data.conversation.customerId,
|
||||||
|
last_message_at: data.conversation.lastMessageAt,
|
||||||
|
tenant_id: data.conversation.tenantId,
|
||||||
|
ticket_number: data.conversation.ticketNumber,
|
||||||
|
helpdesk_contacts: data.contact,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// 🔄 4. Konversation Status ändern
|
// 🔄 4. Konversation Status ändern
|
||||||
@@ -181,36 +203,39 @@ const helpdeskRoutes: FastifyPluginAsync = async (server) => {
|
|||||||
safeConfig.smtp.pass = encrypt(safeConfig.smtp.pass)
|
safeConfig.smtp.pass = encrypt(safeConfig.smtp.pass)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Speichern in Supabase
|
const inserted = await server.db
|
||||||
const { data, error } = await server.supabase
|
.insert(helpdesk_channel_instances)
|
||||||
.from("helpdesk_channel_instances")
|
.values({
|
||||||
.insert({
|
tenantId: tenant_id,
|
||||||
tenant_id,
|
typeId: type_id,
|
||||||
type_id,
|
|
||||||
name,
|
name,
|
||||||
config: safeConfig,
|
config: safeConfig,
|
||||||
is_active,
|
isActive: is_active,
|
||||||
})
|
})
|
||||||
.select()
|
.returning()
|
||||||
.single()
|
|
||||||
|
|
||||||
if (error) throw error
|
const data = inserted[0]
|
||||||
|
if (!data) throw new Error("Konnte Channel nicht erstellen")
|
||||||
|
const responseConfig: any = data.config
|
||||||
|
|
||||||
// sensible Felder aus Response entfernen
|
// sensible Felder aus Response entfernen
|
||||||
if (data.config?.imap) {
|
if (responseConfig?.imap) {
|
||||||
delete data.config.imap.host
|
delete responseConfig.imap.host
|
||||||
delete data.config.imap.user
|
delete responseConfig.imap.user
|
||||||
delete data.config.imap.pass
|
delete responseConfig.imap.pass
|
||||||
}
|
}
|
||||||
if (data.config?.smtp) {
|
if (responseConfig?.smtp) {
|
||||||
delete data.config.smtp.host
|
delete responseConfig.smtp.host
|
||||||
delete data.config.smtp.user
|
delete responseConfig.smtp.user
|
||||||
delete data.config.smtp.pass
|
delete responseConfig.smtp.pass
|
||||||
}
|
}
|
||||||
|
|
||||||
reply.send({
|
reply.send({
|
||||||
message: "E-Mail-Channel erfolgreich erstellt",
|
message: "E-Mail-Channel erfolgreich erstellt",
|
||||||
channel: data,
|
channel: {
|
||||||
|
...data,
|
||||||
|
config: responseConfig
|
||||||
|
},
|
||||||
})
|
})
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("Fehler bei Channel-Erstellung:", err)
|
console.error("Fehler bei Channel-Erstellung:", err)
|
||||||
@@ -234,29 +259,29 @@ const helpdeskRoutes: FastifyPluginAsync = async (server) => {
|
|||||||
const { text } = req.body as { text: string }
|
const { text } = req.body as { text: string }
|
||||||
|
|
||||||
// 🔹 Konversation inkl. Channel + Kontakt laden
|
// 🔹 Konversation inkl. Channel + Kontakt laden
|
||||||
const { data: conv, error: convErr } = await server.supabase
|
const rows = await server.db
|
||||||
.from("helpdesk_conversations")
|
.select({
|
||||||
.select(`
|
conversation: helpdesk_conversations,
|
||||||
id,
|
contact: helpdesk_contacts,
|
||||||
tenant_id,
|
channel: helpdesk_channel_instances,
|
||||||
subject,
|
})
|
||||||
channel_instance_id,
|
.from(helpdesk_conversations)
|
||||||
helpdesk_contacts(email),
|
.leftJoin(helpdesk_contacts, eq(helpdesk_contacts.id, helpdesk_conversations.contactId))
|
||||||
helpdesk_channel_instances(config, name),
|
.leftJoin(helpdesk_channel_instances, eq(helpdesk_channel_instances.id, helpdesk_conversations.channelInstanceId))
|
||||||
ticket_number
|
.where(eq(helpdesk_conversations.id, conversationId))
|
||||||
`)
|
.limit(1)
|
||||||
.eq("id", conversationId)
|
|
||||||
.single()
|
const conv = rows[0]
|
||||||
|
|
||||||
console.log(conv)
|
console.log(conv)
|
||||||
|
|
||||||
if (convErr || !conv) {
|
if (!conv) {
|
||||||
reply.status(404).send({ error: "Konversation nicht gefunden" })
|
reply.status(404).send({ error: "Konversation nicht gefunden" })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const contact = conv.helpdesk_contacts as unknown as {email: string}
|
const contact = conv.contact as unknown as {email: string}
|
||||||
const channel = conv.helpdesk_channel_instances as unknown as {name: string}
|
const channel = conv.channel as unknown as {name: string, config: any}
|
||||||
|
|
||||||
console.log(contact)
|
console.log(contact)
|
||||||
if (!contact?.email) {
|
if (!contact?.email) {
|
||||||
@@ -288,7 +313,7 @@ const helpdeskRoutes: FastifyPluginAsync = async (server) => {
|
|||||||
const mailOptions = {
|
const mailOptions = {
|
||||||
from: `"${channel?.name}" <${user}>`,
|
from: `"${channel?.name}" <${user}>`,
|
||||||
to: contact.email,
|
to: contact.email,
|
||||||
subject: `${conv.ticket_number} | ${conv.subject}` || `${conv.ticket_number} | Antwort vom FEDEO Helpdesk`,
|
subject: `${conv.conversation.ticketNumber} | ${conv.conversation.subject}` || `${conv.conversation.ticketNumber} | Antwort vom FEDEO Helpdesk`,
|
||||||
text,
|
text,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -296,24 +321,22 @@ const helpdeskRoutes: FastifyPluginAsync = async (server) => {
|
|||||||
console.log(`[Helpdesk SMTP] Gesendet an ${contact.email}: ${info.messageId}`)
|
console.log(`[Helpdesk SMTP] Gesendet an ${contact.email}: ${info.messageId}`)
|
||||||
|
|
||||||
// 💾 Nachricht speichern
|
// 💾 Nachricht speichern
|
||||||
const { error: insertErr } = await server.supabase
|
await server.db
|
||||||
.from("helpdesk_messages")
|
.insert(helpdesk_messages)
|
||||||
.insert({
|
.values({
|
||||||
tenant_id: conv.tenant_id,
|
tenantId: conv.conversation.tenantId,
|
||||||
conversation_id: conversationId,
|
conversationId: conversationId,
|
||||||
direction: "outgoing",
|
direction: "outgoing",
|
||||||
payload: { type: "text", text },
|
payload: { type: "text", text },
|
||||||
external_message_id: info.messageId,
|
externalMessageId: info.messageId,
|
||||||
received_at: new Date().toISOString(),
|
receivedAt: new Date(),
|
||||||
})
|
})
|
||||||
|
|
||||||
if (insertErr) throw insertErr
|
|
||||||
|
|
||||||
// 🔁 Konversation aktualisieren
|
// 🔁 Konversation aktualisieren
|
||||||
await server.supabase
|
await server.db
|
||||||
.from("helpdesk_conversations")
|
.update(helpdesk_conversations)
|
||||||
.update({ last_message_at: new Date().toISOString() })
|
.set({ lastMessageAt: new Date() })
|
||||||
.eq("id", conversationId)
|
.where(eq(helpdesk_conversations.id, conversationId))
|
||||||
|
|
||||||
reply.send({
|
reply.send({
|
||||||
message: "E-Mail erfolgreich gesendet",
|
message: "E-Mail erfolgreich gesendet",
|
||||||
|
|||||||
@@ -1,12 +1,34 @@
|
|||||||
// src/routes/resources/history.ts
|
// src/routes/resources/history.ts
|
||||||
import { FastifyInstance } from "fastify";
|
import { FastifyInstance } from "fastify";
|
||||||
|
import { and, asc, eq, inArray } from "drizzle-orm";
|
||||||
|
import { authProfiles, historyitems } from "../../db/schema";
|
||||||
|
|
||||||
const columnMap: Record<string, string> = {
|
const columnMap: Record<string, any> = {
|
||||||
|
customers: historyitems.customer,
|
||||||
|
vendors: historyitems.vendor,
|
||||||
|
projects: historyitems.project,
|
||||||
|
plants: historyitems.plant,
|
||||||
|
contacts: historyitems.contact,
|
||||||
|
tasks: historyitems.task,
|
||||||
|
vehicles: historyitems.vehicle,
|
||||||
|
events: historyitems.event,
|
||||||
|
files: historyitems.file,
|
||||||
|
products: historyitems.product,
|
||||||
|
inventoryitems: historyitems.inventoryitem,
|
||||||
|
inventoryitemgroups: historyitems.inventoryitemgroup,
|
||||||
|
checks: historyitems.check,
|
||||||
|
costcentres: historyitems.costcentre,
|
||||||
|
ownaccounts: historyitems.ownaccount,
|
||||||
|
documentboxes: historyitems.documentbox,
|
||||||
|
hourrates: historyitems.hourrate,
|
||||||
|
services: historyitems.service,
|
||||||
|
};
|
||||||
|
|
||||||
|
const insertFieldMap: Record<string, string> = {
|
||||||
customers: "customer",
|
customers: "customer",
|
||||||
vendors: "vendor",
|
vendors: "vendor",
|
||||||
projects: "project",
|
projects: "project",
|
||||||
plants: "plant",
|
plants: "plant",
|
||||||
contracts: "contract",
|
|
||||||
contacts: "contact",
|
contacts: "contact",
|
||||||
tasks: "task",
|
tasks: "task",
|
||||||
vehicles: "vehicle",
|
vehicles: "vehicle",
|
||||||
@@ -15,15 +37,18 @@ const columnMap: Record<string, string> = {
|
|||||||
products: "product",
|
products: "product",
|
||||||
inventoryitems: "inventoryitem",
|
inventoryitems: "inventoryitem",
|
||||||
inventoryitemgroups: "inventoryitemgroup",
|
inventoryitemgroups: "inventoryitemgroup",
|
||||||
absencerequests: "absencerequest",
|
|
||||||
checks: "check",
|
checks: "check",
|
||||||
costcentres: "costcentre",
|
costcentres: "costcentre",
|
||||||
ownaccounts: "ownaccount",
|
ownaccounts: "ownaccount",
|
||||||
documentboxes: "documentbox",
|
documentboxes: "documentbox",
|
||||||
hourrates: "hourrate",
|
hourrates: "hourrate",
|
||||||
services: "service",
|
services: "service",
|
||||||
roles: "role",
|
}
|
||||||
};
|
|
||||||
|
const parseId = (value: string) => {
|
||||||
|
if (/^\d+$/.test(value)) return Number(value)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
export default async function resourceHistoryRoutes(server: FastifyInstance) {
|
export default async function resourceHistoryRoutes(server: FastifyInstance) {
|
||||||
server.get<{
|
server.get<{
|
||||||
@@ -49,29 +74,36 @@ export default async function resourceHistoryRoutes(server: FastifyInstance) {
|
|||||||
return reply.code(400).send({ error: `History not supported for resource '${resource}'` });
|
return reply.code(400).send({ error: `History not supported for resource '${resource}'` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
const data = await server.db
|
||||||
.from("historyitems")
|
.select()
|
||||||
.select("*")
|
.from(historyitems)
|
||||||
.eq(column, id)
|
.where(eq(column, parseId(id)))
|
||||||
.order("created_at", { ascending: true });
|
.orderBy(asc(historyitems.createdAt));
|
||||||
|
|
||||||
if (error) {
|
const userIds = Array.from(
|
||||||
server.log.error(error);
|
new Set(data.map((item) => item.createdBy).filter(Boolean))
|
||||||
return reply.code(500).send({ error: "Failed to fetch history" });
|
) as string[]
|
||||||
}
|
|
||||||
|
|
||||||
const {data:users, error:usersError} = await server.supabase
|
const profiles = userIds.length > 0
|
||||||
.from("auth_users")
|
? await server.db
|
||||||
.select("*, auth_profiles(*), tenants!auth_tenant_users(*)")
|
.select()
|
||||||
|
.from(authProfiles)
|
||||||
|
.where(and(
|
||||||
|
eq(authProfiles.tenant_id, req.user?.tenant_id),
|
||||||
|
inArray(authProfiles.user_id, userIds)
|
||||||
|
))
|
||||||
|
: []
|
||||||
|
|
||||||
const filteredUsers = (users ||[]).filter(i => i.tenants.find((t:any) => t.id === req.user?.tenant_id))
|
const profileByUserId = new Map(
|
||||||
|
profiles.map((profile) => [profile.user_id, profile])
|
||||||
|
)
|
||||||
|
|
||||||
const dataCombined = data.map(historyitem => {
|
const dataCombined = data.map((historyitem) => ({
|
||||||
return {
|
...historyitem,
|
||||||
...historyitem,
|
created_at: historyitem.createdAt,
|
||||||
created_by_profile: filteredUsers.find(i => i.id === historyitem.created_by) ? filteredUsers.find(i => i.id === historyitem.created_by).auth_profiles[0] : null
|
created_by: historyitem.createdBy,
|
||||||
}
|
created_by_profile: historyitem.createdBy ? profileByUserId.get(historyitem.createdBy) || null : null,
|
||||||
})
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -128,29 +160,33 @@ export default async function resourceHistoryRoutes(server: FastifyInstance) {
|
|||||||
const userId = (req.user as any)?.user_id;
|
const userId = (req.user as any)?.user_id;
|
||||||
|
|
||||||
|
|
||||||
const fkField = columnMap[resource];
|
const fkField = insertFieldMap[resource];
|
||||||
if (!fkField) {
|
if (!fkField) {
|
||||||
return reply.code(400).send({ error: `Unknown resource: ${resource}` });
|
return reply.code(400).send({ error: `Unknown resource: ${resource}` });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
const inserted = await server.db
|
||||||
.from("historyitems")
|
.insert(historyitems)
|
||||||
.insert({
|
.values({
|
||||||
text,
|
text,
|
||||||
[fkField]: id,
|
[fkField]: parseId(id),
|
||||||
oldVal: old_val || null,
|
oldVal: old_val || null,
|
||||||
newVal: new_val || null,
|
newVal: new_val || null,
|
||||||
config: config || null,
|
config: config || null,
|
||||||
tenant: (req.user as any)?.tenant_id,
|
tenant: (req.user as any)?.tenant_id,
|
||||||
created_by: userId
|
createdBy: userId
|
||||||
})
|
})
|
||||||
.select()
|
.returning()
|
||||||
.single();
|
|
||||||
|
|
||||||
if (error) {
|
const data = inserted[0]
|
||||||
return reply.code(500).send({ error: error.message });
|
if (!data) {
|
||||||
|
return reply.code(500).send({ error: "Failed to create history entry" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return reply.code(201).send(data);
|
return reply.code(201).send({
|
||||||
|
...data,
|
||||||
|
created_at: data.createdAt,
|
||||||
|
created_by: data.createdBy
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
// routes/notifications.routes.ts
|
// routes/notifications.routes.ts
|
||||||
import { FastifyInstance } from 'fastify';
|
import { FastifyInstance } from 'fastify';
|
||||||
import { NotificationService, UserDirectory } from '../modules/notification.service';
|
import { NotificationService, UserDirectory } from '../modules/notification.service';
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { authUsers } from "../../db/schema";
|
||||||
|
|
||||||
// Beispiel: E-Mail aus eigener User-Tabelle laden
|
// Beispiel: E-Mail aus eigener User-Tabelle laden
|
||||||
const getUserDirectory: UserDirectory = async (server:FastifyInstance, userId, tenantId) => {
|
const getUserDirectory: UserDirectory = async (server:FastifyInstance, userId, tenantId) => {
|
||||||
const { data, error } = await server.supabase
|
const rows = await server.db
|
||||||
.from('auth_users')
|
.select({ email: authUsers.email })
|
||||||
.select('email')
|
.from(authUsers)
|
||||||
.eq('id', userId)
|
.where(eq(authUsers.id, userId))
|
||||||
.maybeSingle();
|
.limit(1)
|
||||||
if (error || !data) return null;
|
const data = rows[0]
|
||||||
|
if (!data) return null;
|
||||||
return { email: data.email };
|
return { email: data.email };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function notificationsRoutes(server: FastifyInstance) {
|
export default async function notificationsRoutes(server: FastifyInstance) {
|
||||||
// wichtig: server.supabase ist über app verfügbar
|
|
||||||
|
|
||||||
const svc = new NotificationService(server, getUserDirectory);
|
const svc = new NotificationService(server, getUserDirectory);
|
||||||
|
|
||||||
server.post('/notifications/trigger', async (req, reply) => {
|
server.post('/notifications/trigger', async (req, reply) => {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ export default async function resourceRoutesSpecial(server: FastifyInstance) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
// 📌 SELECT: wir ignorieren select string (wie Supabase)
|
// 📌 SELECT: select-string wird in dieser Route bewusst ignoriert
|
||||||
// Drizzle kann kein dynamisches Select aus String!
|
// Drizzle kann kein dynamisches Select aus String!
|
||||||
// Wir geben IMMER alle Spalten zurück → kompatibel zum Frontend
|
// Wir geben IMMER alle Spalten zurück → kompatibel zum Frontend
|
||||||
// ---------------------------------------
|
// ---------------------------------------
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import { FastifyInstance } from 'fastify'
|
import { FastifyInstance } from 'fastify'
|
||||||
import { StaffTimeEntryConnect } from '../../types/staff'
|
import { StaffTimeEntryConnect } from '../../types/staff'
|
||||||
|
import { asc, eq } from "drizzle-orm";
|
||||||
|
import { stafftimenetryconnects } from "../../../db/schema";
|
||||||
|
|
||||||
export default async function staffTimeConnectRoutes(server: FastifyInstance) {
|
export default async function staffTimeConnectRoutes(server: FastifyInstance) {
|
||||||
|
|
||||||
@@ -8,16 +10,21 @@ export default async function staffTimeConnectRoutes(server: FastifyInstance) {
|
|||||||
'/staff/time/:id/connects',
|
'/staff/time/:id/connects',
|
||||||
async (req, reply) => {
|
async (req, reply) => {
|
||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
const { started_at, stopped_at, project_id, customer_id, task_id, ticket_id, notes } = req.body
|
const { started_at, stopped_at, project_id, notes } = req.body
|
||||||
|
const parsedProjectId = project_id ? Number(project_id) : null
|
||||||
|
|
||||||
const { data, error } = await server.supabase
|
const data = await server.db
|
||||||
.from('staff_time_entry_connects')
|
.insert(stafftimenetryconnects)
|
||||||
.insert([{ time_entry_id: id, started_at, stopped_at, project_id, customer_id, task_id, ticket_id, notes }])
|
.values({
|
||||||
.select()
|
stafftimeentry: id,
|
||||||
.maybeSingle()
|
started_at: new Date(started_at),
|
||||||
|
stopped_at: new Date(stopped_at),
|
||||||
|
project_id: parsedProjectId,
|
||||||
|
notes
|
||||||
|
})
|
||||||
|
.returning()
|
||||||
|
|
||||||
if (error) return reply.code(400).send({ error: error.message })
|
return reply.send(data[0])
|
||||||
return reply.send(data)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,13 +33,12 @@ export default async function staffTimeConnectRoutes(server: FastifyInstance) {
|
|||||||
'/staff/time/:id/connects',
|
'/staff/time/:id/connects',
|
||||||
async (req, reply) => {
|
async (req, reply) => {
|
||||||
const { id } = req.params
|
const { id } = req.params
|
||||||
const { data, error } = await server.supabase
|
const data = await server.db
|
||||||
.from('staff_time_entry_connects')
|
.select()
|
||||||
.select('*')
|
.from(stafftimenetryconnects)
|
||||||
.eq('time_entry_id', id)
|
.where(eq(stafftimenetryconnects.stafftimeentry, id))
|
||||||
.order('started_at', { ascending: true })
|
.orderBy(asc(stafftimenetryconnects.started_at))
|
||||||
|
|
||||||
if (error) return reply.code(400).send({ error: error.message })
|
|
||||||
return reply.send(data)
|
return reply.send(data)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -42,15 +48,20 @@ export default async function staffTimeConnectRoutes(server: FastifyInstance) {
|
|||||||
'/staff/time/connects/:connectId',
|
'/staff/time/connects/:connectId',
|
||||||
async (req, reply) => {
|
async (req, reply) => {
|
||||||
const { connectId } = req.params
|
const { connectId } = req.params
|
||||||
const { data, error } = await server.supabase
|
const patchData = { ...req.body } as any
|
||||||
.from('staff_time_entry_connects')
|
if (patchData.started_at) patchData.started_at = new Date(patchData.started_at)
|
||||||
.update({ ...req.body, updated_at: new Date().toISOString() })
|
if (patchData.stopped_at) patchData.stopped_at = new Date(patchData.stopped_at)
|
||||||
.eq('id', connectId)
|
if (patchData.project_id !== undefined) {
|
||||||
.select()
|
patchData.project_id = patchData.project_id ? Number(patchData.project_id) : null
|
||||||
.maybeSingle()
|
}
|
||||||
|
|
||||||
if (error) return reply.code(400).send({ error: error.message })
|
const data = await server.db
|
||||||
return reply.send(data)
|
.update(stafftimenetryconnects)
|
||||||
|
.set({ ...patchData, updated_at: new Date() })
|
||||||
|
.where(eq(stafftimenetryconnects.id, connectId))
|
||||||
|
.returning()
|
||||||
|
|
||||||
|
return reply.send(data[0])
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -59,12 +70,10 @@ export default async function staffTimeConnectRoutes(server: FastifyInstance) {
|
|||||||
'/staff/time/connects/:connectId',
|
'/staff/time/connects/:connectId',
|
||||||
async (req, reply) => {
|
async (req, reply) => {
|
||||||
const { connectId } = req.params
|
const { connectId } = req.params
|
||||||
const { error } = await server.supabase
|
await server.db
|
||||||
.from('staff_time_entry_connects')
|
.delete(stafftimenetryconnects)
|
||||||
.delete()
|
.where(eq(stafftimenetryconnects.id, connectId))
|
||||||
.eq('id', connectId)
|
|
||||||
|
|
||||||
if (error) return reply.code(400).send({ error: error.message })
|
|
||||||
return reply.send({ success: true })
|
return reply.send({ success: true })
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,12 +1,25 @@
|
|||||||
import xmlbuilder from "xmlbuilder";
|
import xmlbuilder from "xmlbuilder";
|
||||||
import {randomUUID} from "node:crypto";
|
import {randomUUID} from "node:crypto";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import { and, eq, inArray } from "drizzle-orm";
|
||||||
|
import { createddocuments, tenants } from "../../../db/schema";
|
||||||
|
|
||||||
export const createSEPAExport = async (server,idsToExport, tenant_id) => {
|
export const createSEPAExport = async (server,idsToExport, tenant_id) => {
|
||||||
const {data,error} = await server.supabase.from("createddocuments").select().eq("tenant", tenant_id).in("id", idsToExport)
|
const data = await server.db
|
||||||
const {data:tenantData,error:tenantError} = await server.supabase.from("tenants").select().eq("id", tenant_id).single()
|
.select()
|
||||||
|
.from(createddocuments)
|
||||||
|
.where(and(
|
||||||
|
eq(createddocuments.tenant, tenant_id),
|
||||||
|
inArray(createddocuments.id, idsToExport)
|
||||||
|
))
|
||||||
|
|
||||||
|
const tenantRows = await server.db
|
||||||
|
.select()
|
||||||
|
.from(tenants)
|
||||||
|
.where(eq(tenants.id, tenant_id))
|
||||||
|
.limit(1)
|
||||||
|
const tenantData = tenantRows[0]
|
||||||
console.log(tenantData)
|
console.log(tenantData)
|
||||||
console.log(tenantError)
|
|
||||||
|
|
||||||
console.log(data)
|
console.log(data)
|
||||||
|
|
||||||
@@ -111,4 +124,4 @@ export const createSEPAExport = async (server,idsToExport, tenant_id) => {
|
|||||||
|
|
||||||
console.log(doc.end({pretty:true}))
|
console.log(doc.end({pretty:true}))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ const InstructionFormat = z.object({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
// MAIN FUNCTION – REPLACES SUPABASE VERSION
|
// MAIN FUNCTION
|
||||||
// ---------------------------------------------------------
|
// ---------------------------------------------------------
|
||||||
export const getInvoiceDataFromGPT = async function (
|
export const getInvoiceDataFromGPT = async function (
|
||||||
server: FastifyInstance,
|
server: FastifyInstance,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { FastifyInstance } from "fastify"
|
import { FastifyInstance } from "fastify"
|
||||||
|
import { historyitems } from "../../db/schema";
|
||||||
|
|
||||||
export async function insertHistoryItem(
|
export async function insertHistoryItem(
|
||||||
server: FastifyInstance,
|
server: FastifyInstance,
|
||||||
@@ -63,8 +64,5 @@ export async function insertHistoryItem(
|
|||||||
newVal: params.newVal ? JSON.stringify(params.newVal) : null
|
newVal: params.newVal ? JSON.stringify(params.newVal) : null
|
||||||
}
|
}
|
||||||
|
|
||||||
const { error } = await server.supabase.from("historyitems").insert([entry])
|
await server.db.insert(historyitems).values(entry as any)
|
||||||
if (error) { // @ts-ignore
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import {PDFDocument, StandardFonts, rgb} from "pdf-lib"
|
|||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
import {renderAsCurrency, splitStringBySpace} from "./stringRendering";
|
import {renderAsCurrency, splitStringBySpace} from "./stringRendering";
|
||||||
import {FastifyInstance} from "fastify";
|
import {FastifyInstance} from "fastify";
|
||||||
|
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
||||||
|
import { s3 } from "./s3";
|
||||||
|
import { secrets } from "./secrets";
|
||||||
|
|
||||||
const getCoordinatesForPDFLib = (x:number ,y:number, page:any) => {
|
const getCoordinatesForPDFLib = (x:number ,y:number, page:any) => {
|
||||||
/*
|
/*
|
||||||
@@ -25,9 +28,21 @@ const getCoordinatesForPDFLib = (x:number ,y:number, page:any) => {
|
|||||||
|
|
||||||
const getBackgroundSourceBuffer = async (server:FastifyInstance, path:string) => {
|
const getBackgroundSourceBuffer = async (server:FastifyInstance, path:string) => {
|
||||||
|
|
||||||
const {data:backgroundPDFData,error:backgroundPDFError} = await server.supabase.storage.from("files").download(path)
|
console.log(path)
|
||||||
|
|
||||||
return backgroundPDFData.arrayBuffer()
|
const { Body } = await s3.send(
|
||||||
|
new GetObjectCommand({
|
||||||
|
Bucket: secrets.S3_BUCKET,
|
||||||
|
Key: path
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const chunks: Buffer[] = []
|
||||||
|
for await (const chunk of Body as any) {
|
||||||
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk))
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.concat(chunks)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getDuration = (time) => {
|
const getDuration = (time) => {
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ export let secrets = {
|
|||||||
PORT: number
|
PORT: number
|
||||||
HOST: string
|
HOST: string
|
||||||
DATABASE_URL: string
|
DATABASE_URL: string
|
||||||
SUPABASE_URL: string
|
|
||||||
SUPABASE_SERVICE_ROLE_KEY: string
|
|
||||||
S3_BUCKET: string
|
S3_BUCKET: string
|
||||||
ENCRYPTION_KEY: string
|
ENCRYPTION_KEY: string
|
||||||
MAILER_SMTP_HOST: string
|
MAILER_SMTP_HOST: string
|
||||||
|
|||||||
@@ -60,7 +60,6 @@ const router = useRouter()
|
|||||||
const createddocuments = ref([])
|
const createddocuments = ref([])
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
//createddocuments.value = (await useSupabaseSelect("createddocuments")).filter(i => !i.archived)
|
|
||||||
createddocuments.value = (await useEntities("createddocuments").select()).filter(i => !i.archived)
|
createddocuments.value = (await useEntities("createddocuments").select()).filter(i => !i.archived)
|
||||||
}
|
}
|
||||||
setup()
|
setup()
|
||||||
@@ -308,4 +307,4 @@ const selectItem = (item) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -28,8 +25,6 @@ const statementallocations = ref([])
|
|||||||
const incominginvoices = ref([])
|
const incominginvoices = ref([])
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
//statementallocations.value = (await supabase.from("statementallocations").select("*, bs_id(*)").eq("account", route.params.id).eq("tenant",profileStore.currentTenant).order("created_at",{ascending: true})).data
|
|
||||||
//incominginvoices.value = (await useSupabaseSelect("incominginvoices", "*, vendor(*)")).filter(i => i.accounts.find(x => x.account == route.params.id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
@@ -105,4 +100,4 @@ const renderedAllocations = computed(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ const emit = defineEmits(["updateNeeded"]);
|
|||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const renderedPhases = computed(() => {
|
const renderedPhases = computed(() => {
|
||||||
if(props.topLevelType === "projects" && props.item.phases) {
|
if(props.topLevelType === "projects" && props.item.phases) {
|
||||||
@@ -77,17 +76,6 @@ const changeActivePhase = async (key) => {
|
|||||||
|
|
||||||
const res = await useEntities("projects").update(item.id, {phases:item.phases,active_phase: item.phases.find(i => i.active).label})
|
const res = await useEntities("projects").update(item.id, {phases:item.phases,active_phase: item.phases.find(i => i.active).label})
|
||||||
|
|
||||||
//const {error:updateError} = await supabase.from("projects").update({phases: item.phases}).eq("id",item.id)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*const {error} = await supabase.from("historyitems").insert({
|
|
||||||
createdBy: profileStore.activeProfile.id,
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
text: `Aktive Phase zu "${phaseLabel}" gewechselt`,
|
|
||||||
project: item.id
|
|
||||||
})*/
|
|
||||||
|
|
||||||
emit("updateNeeded")
|
emit("updateNeeded")
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -166,4 +154,4 @@ const changeActivePhase = async (key) => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,10 +1,5 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const route = useRoute()
|
|
||||||
const router = useRouter()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
queryStringData: {
|
queryStringData: {
|
||||||
@@ -114,4 +109,4 @@ const columns = [
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
const selectedItem = ref(0)
|
const selectedItem = ref(0)
|
||||||
const sort = ref({
|
const sort = ref({
|
||||||
column: dataType.supabaseSortColumn || "date",
|
column: dataType.sortColumn || "date",
|
||||||
direction: 'desc'
|
direction: 'desc'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,17 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
|
|
||||||
const globalMessages = ref([])
|
const globalMessages = ref([])
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
let {data} = await supabase.from("globalmessages").select("*, profiles(id)")
|
let data = []
|
||||||
|
try {
|
||||||
|
data = await useNuxtApp().$api("/api/resource/globalmessages")
|
||||||
|
} catch (e) {
|
||||||
|
data = []
|
||||||
|
}
|
||||||
|
|
||||||
data = data.filter((message) => message.profiles.length === 0)
|
data = (data || []).filter((message) => !message.profiles || message.profiles.length === 0)
|
||||||
|
|
||||||
globalMessages.value = data
|
globalMessages.value = data
|
||||||
|
|
||||||
@@ -29,10 +32,17 @@ const showMessage = (message) => {
|
|||||||
showMessageModal.value = true
|
showMessageModal.value = true
|
||||||
}
|
}
|
||||||
const markMessageAsRead = async () => {
|
const markMessageAsRead = async () => {
|
||||||
await supabase.from("globalmessagesseen").insert({
|
try {
|
||||||
profile: profileStore.activeProfile.id,
|
await useNuxtApp().$api("/api/resource/globalmessagesseen", {
|
||||||
message: messageToShow.value.id,
|
method: "POST",
|
||||||
})
|
body: {
|
||||||
|
profile: profileStore.activeProfile.id,
|
||||||
|
message: messageToShow.value.id,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
// noop: endpoint optional in newer backend versions
|
||||||
|
}
|
||||||
showMessageModal.value = false
|
showMessageModal.value = false
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
@@ -86,4 +96,4 @@ setup()
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,10 +3,7 @@ const { isHelpSlideoverOpen } = useDashboard()
|
|||||||
const { metaSymbol } = useShortcuts()
|
const { metaSymbol } = useShortcuts()
|
||||||
|
|
||||||
const shortcuts = ref(false)
|
const shortcuts = ref(false)
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const query = ref('')
|
const query = ref('')
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@@ -228,4 +225,4 @@ const resetContactRequest = () => {
|
|||||||
</div>
|
</div>
|
||||||
<UProgress class="mt-5" animation="carousel" v-else/>-->
|
<UProgress class="mt-5" animation="carousel" v-else/>-->
|
||||||
</UDashboardSlideover>
|
</UDashboardSlideover>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import {formatTimeAgo} from '@vueuse/core'
|
import {formatTimeAgo} from '@vueuse/core'
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
|
|
||||||
const { isNotificationsSlideoverOpen } = useDashboard()
|
const { isNotificationsSlideoverOpen } = useDashboard()
|
||||||
|
|
||||||
watch(isNotificationsSlideoverOpen, async (newVal,oldVal) => {
|
watch(isNotificationsSlideoverOpen, async (newVal,oldVal) => {
|
||||||
@@ -15,18 +12,24 @@ watch(isNotificationsSlideoverOpen, async (newVal,oldVal) => {
|
|||||||
const notifications = ref([])
|
const notifications = ref([])
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
notifications.value = (await supabase.from("notifications").select()).data
|
try {
|
||||||
|
notifications.value = await useNuxtApp().$api("/api/resource/notifications_items")
|
||||||
|
} catch (e) {
|
||||||
|
notifications.value = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
const setNotificationAsRead = async (notification) => {
|
const setNotificationAsRead = async (notification) => {
|
||||||
console.log(notification)
|
try {
|
||||||
|
await useNuxtApp().$api(`/api/resource/notifications_items/${notification.id}`, {
|
||||||
const {data,error} = await supabase.from("notifications").update({read: true}).eq("id", notification.id)
|
method: "PUT",
|
||||||
|
body: { readAt: new Date() }
|
||||||
console.log(error)
|
})
|
||||||
|
} catch (e) {
|
||||||
|
// noop: endpoint optional in older/newer backend variants
|
||||||
|
}
|
||||||
setup()
|
setup()
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -41,7 +44,7 @@ const setNotificationAsRead = async (notification) => {
|
|||||||
class="p-3 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800/50 cursor-pointer flex items-center gap-3 relative"
|
class="p-3 rounded-md hover:bg-gray-50 dark:hover:bg-gray-800/50 cursor-pointer flex items-center gap-3 relative"
|
||||||
@click="setNotificationAsRead(notification)"
|
@click="setNotificationAsRead(notification)"
|
||||||
>
|
>
|
||||||
<UChip color="primary" :show="!notification.read" inset>
|
<UChip color="primary" :show="!notification.read && !notification.readAt" inset>
|
||||||
<UAvatar alt="FEDEO" size="md" />
|
<UAvatar alt="FEDEO" size="md" />
|
||||||
</UChip>
|
</UChip>
|
||||||
|
|
||||||
@@ -49,7 +52,7 @@ const setNotificationAsRead = async (notification) => {
|
|||||||
<p class="flex items-center justify-between">
|
<p class="flex items-center justify-between">
|
||||||
<span class="text-gray-900 dark:text-white font-medium">{{notification.title}}</span>
|
<span class="text-gray-900 dark:text-white font-medium">{{notification.title}}</span>
|
||||||
|
|
||||||
<time :datetime="notification.date" class="text-gray-500 dark:text-gray-400 text-xs" v-text="formatTimeAgo(new Date(notification.created_at))" />
|
<time :datetime="notification.date || notification.createdAt || notification.created_at" class="text-gray-500 dark:text-gray-400 text-xs" v-text="formatTimeAgo(new Date(notification.createdAt || notification.created_at))" />
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-500 dark:text-gray-400">
|
<p class="text-gray-500 dark:text-gray-400">
|
||||||
{{ notification.message }}
|
{{ notification.message }}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const modal = useModal()
|
const modal = useModal()
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
type: {
|
type: {
|
||||||
@@ -35,11 +34,10 @@ const item = ref({})
|
|||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
if(props.mode === "show") {
|
if(props.mode === "show") {
|
||||||
//Load Data for Show
|
//Load Data for Show
|
||||||
item.value = await useEntities(props.type).selectSingle(props.id, dataType.supabaseSelectWithInformation || "*")
|
item.value = await useEntities(props.type).selectSingle(props.id, dataType.selectWithInformation || "*")
|
||||||
} else if(props.mode === "edit") {
|
} else if(props.mode === "edit") {
|
||||||
//Load Data for Edit
|
//Load Data for Edit
|
||||||
const data = JSON.stringify(await useEntities(props.type).selectSingle(props.id)/*(await supabase.from(props.type).select().eq("id", props.id).single()).data*/)
|
const data = JSON.stringify(await useEntities(props.type).selectSingle(props.id))
|
||||||
//await useSupabaseSelectSingle(type, route.params.id)
|
|
||||||
item.value = data
|
item.value = data
|
||||||
|
|
||||||
} else if(props.mode === "create") {
|
} else if(props.mode === "create") {
|
||||||
@@ -48,7 +46,7 @@ const setupPage = async () => {
|
|||||||
|
|
||||||
} else if(props.mode === "list") {
|
} else if(props.mode === "list") {
|
||||||
//Load Data for List
|
//Load Data for List
|
||||||
items.value = await useEntities(props.type).select(dataType.supabaseSelectWithInformation || "*", dataType.supabaseSortColumn,dataType.supabaseSortAscending || false)
|
items.value = await useEntities(props.type).select(dataType.selectWithInformation || "*", dataType.sortColumn,dataType.sortAscending || false)
|
||||||
}
|
}
|
||||||
|
|
||||||
loaded.value = true
|
loaded.value = true
|
||||||
@@ -95,4 +93,4 @@ setupPage()
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,11 +3,6 @@
|
|||||||
const { isHelpSlideoverOpen } = useDashboard()
|
const { isHelpSlideoverOpen } = useDashboard()
|
||||||
const { isDashboardSearchModalOpen } = useUIState()
|
const { isDashboardSearchModalOpen } = useUIState()
|
||||||
const { metaSymbol } = useShortcuts()
|
const { metaSymbol } = useShortcuts()
|
||||||
const user = useSupabaseUser()
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const router = useRouter()
|
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const items = computed(() => [
|
const items = computed(() => [
|
||||||
@@ -59,4 +54,4 @@ const items = computed(() => [
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</UDropdown>
|
</UDropdown>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
|
|
||||||
let inventoryitemgroups = await Promise.all(props.row.inventoryitemgroups.map(async (i) => {
|
let inventoryitemgroups = await Promise.all(props.row.inventoryitemgroups.map(async (i) => {
|
||||||
return (await supabase.from("inventoryitemgroups").select("id,name").eq("id",i).single()).data.name
|
const group = await useEntities("inventoryitemgroups").selectSingle(i)
|
||||||
|
return group?.name
|
||||||
}))
|
}))
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
|
|
||||||
let inventoryitems = await Promise.all(props.row.inventoryitems.map(async (i) => {
|
let inventoryitems = await Promise.all(props.row.inventoryitems.map(async (i) => {
|
||||||
return (await supabase.from("inventoryitems").select("id,name").eq("id",i).single()).data.name
|
const item = await useEntities("inventoryitems").selectSingle(i)
|
||||||
|
return item?.name
|
||||||
}))
|
}))
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,11 +7,9 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
|
|
||||||
let vehicles = await Promise.all(props.row.vehicles.map(async (i) => {
|
let vehicles = await Promise.all(props.row.vehicles.map(async (i) => {
|
||||||
return (await supabase.from("vehicles").select("id,licensePlate").eq("id",i).single()).data.licensePlate
|
const vehicle = await useEntities("vehicles").selectSingle(i)
|
||||||
|
return vehicle?.licensePlate
|
||||||
}))
|
}))
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
required: true,
|
required: true,
|
||||||
@@ -27,4 +23,4 @@ setupPage()
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -8,15 +8,12 @@ let incomeData = ref({})
|
|||||||
let expenseData = ref({})
|
let expenseData = ref({})
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
//let incomeRawData = (await supabase.from("createddocuments").select().eq("tenant",profileStore.currentTenant).eq("state","Gebucht").in('type',['invoices','advanceInvoices','cancellationInvoices'])).data
|
|
||||||
let incomeRawData = (await useEntities("createddocuments").select()).filter(i => i.state === "Gebucht" && ['invoices','advanceInvoices','cancellationInvoices'].includes(i.type))
|
let incomeRawData = (await useEntities("createddocuments").select()).filter(i => i.state === "Gebucht" && ['invoices','advanceInvoices','cancellationInvoices'].includes(i.type))
|
||||||
|
|
||||||
let incomeRawFilteredData = incomeRawData.filter(x => x.state === 'Gebucht' && incomeRawData.find(i => i.linkedDocument && i.linkedDocument.id === x.id && i.type === 'cancellationInvoices') && ['invoices','advanceInvoices'].includes(row.type))
|
let incomeRawFilteredData = incomeRawData.filter(x => x.state === 'Gebucht' && incomeRawData.find(i => i.linkedDocument && i.linkedDocument.id === x.id && i.type === 'cancellationInvoices') && ['invoices','advanceInvoices'].includes(row.type))
|
||||||
|
|
||||||
|
|
||||||
//let expenseRawData =(await supabase.from("incominginvoices").select().eq("tenant",profileStore.currentTenant)).data
|
|
||||||
let expenseRawData =(await useEntities("incominginvoices").select())
|
let expenseRawData =(await useEntities("incominginvoices").select())
|
||||||
//let withoutInvoiceRawData = (await supabase.from("statementallocations").select().eq("tenant",profileStore.currentTenant).not("account","is",null)).data
|
|
||||||
let withoutInvoiceRawData = (await useEntities("statementallocations").select()).filter(i => i.account)
|
let withoutInvoiceRawData = (await useEntities("statementallocations").select()).filter(i => i.account)
|
||||||
|
|
||||||
let withoutInvoiceRawDataExpenses = []
|
let withoutInvoiceRawDataExpenses = []
|
||||||
@@ -241,4 +238,4 @@ setup()
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const profileStore = useProfileStore();
|
const profileStore = useProfileStore();
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const staffTime = useStaffTime()
|
||||||
|
|
||||||
const runningTimeInfo = ref({})
|
const runningTimeInfo = ref({})
|
||||||
|
|
||||||
@@ -11,12 +11,9 @@ const projects = ref([])
|
|||||||
const platform = ref("default")
|
const platform = ref("default")
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
runningTimeInfo.value = (await supabase.from("times").select().eq("profile", profileStore.activeProfile.id).is("endDate", null).single()).data || {}
|
const rows = await staffTime.list({ user_id: profileStore.activeProfile?.user_id || profileStore.activeProfile?.id })
|
||||||
|
runningTimeInfo.value = rows.find((r) => !r.stopped_at && r.type === "work") || {}
|
||||||
//projects.value = (await useSupabaseSelect("projects"))
|
projects.value = await useEntities("projects").select("*")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPage()
|
setupPage()
|
||||||
@@ -26,47 +23,25 @@ setupPage()
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
const startTime = async () => {
|
const startTime = async () => {
|
||||||
console.log("started")
|
try {
|
||||||
runningTimeInfo.value = {
|
await staffTime.start("Arbeitszeit")
|
||||||
profile: profileStore.activeProfile.id,
|
toast.add({title: "Projektzeit erfolgreich gestartet"})
|
||||||
startDate: dayjs(),
|
await setupPage()
|
||||||
tenant: profileStore.currentTenant,
|
} catch (error) {
|
||||||
state: platform.value === "mobile" ? "In der App gestartet" : "Im Web gestartet",
|
|
||||||
source: "Dashboard"
|
|
||||||
}
|
|
||||||
|
|
||||||
const {data,error} = await supabase
|
|
||||||
.from("times")
|
|
||||||
.insert([runningTimeInfo.value])
|
|
||||||
.select()
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
console.log(error)
|
||||||
toast.add({title: "Fehler beim starten der Projektzeit",color:"rose"})
|
toast.add({title: "Fehler beim starten der Projektzeit",color:"rose"})
|
||||||
} else if(data) {
|
|
||||||
toast.add({title: "Projektzeit erfolgreich gestartet"})
|
|
||||||
runningTimeInfo.value = data[0]
|
|
||||||
//console.log(runningTimeInfo.value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopStartedTime = async () => {
|
const stopStartedTime = async () => {
|
||||||
runningTimeInfo.value.endDate = dayjs()
|
try {
|
||||||
runningTimeInfo.value.state = platform.value === "mobile" ? "In der App gestoppt" : "Im Web gestoppt"
|
await staffTime.stop()
|
||||||
|
|
||||||
const {error,status} = await supabase
|
|
||||||
.from("times")
|
|
||||||
.update(runningTimeInfo.value)
|
|
||||||
.eq('id',runningTimeInfo.value.id)
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
let errorId = await useError().logError(`${status} - ${JSON.stringify(error)}`)
|
|
||||||
toast.add({title: errorId ? `Fehler beim stoppen der Projektzeit (Fehler ID: ${errorId})` : `Fehler beim stoppen der Projektzeit`,color:"rose"})
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
toast.add({title: "Projektzeit erfolgreich gestoppt"})
|
toast.add({title: "Projektzeit erfolgreich gestoppt"})
|
||||||
runningTimeInfo.value = {}
|
runningTimeInfo.value = {}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
let errorId = await useError().logError(`${JSON.stringify(error)}`)
|
||||||
|
toast.add({title: errorId ? `Fehler beim stoppen der Projektzeit (Fehler ID: ${errorId})` : `Fehler beim stoppen der Projektzeit`,color:"rose"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,9 +49,9 @@ const stopStartedTime = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="runningTimeInfo.startDate">
|
<div v-if="runningTimeInfo.started_at">
|
||||||
<p>Start: {{dayjs(runningTimeInfo.startDate).format("HH:mm")}}</p>
|
<p>Start: {{dayjs(runningTimeInfo.started_at).format("HH:mm")}}</p>
|
||||||
<p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') + ' min' }}</p>
|
<p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') + ' min' }}</p>
|
||||||
|
|
||||||
<UFormGroup
|
<UFormGroup
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
@@ -119,4 +94,4 @@ const stopStartedTime = async () => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const profileStore = useProfileStore();
|
const profileStore = useProfileStore();
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
const staffTime = useStaffTime()
|
||||||
|
|
||||||
const runningTimeInfo = ref({})
|
const runningTimeInfo = ref({})
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
runningTimeInfo.value = (await supabase.from("workingtimes").select().eq("profile", profileStore.activeProfile.id).is("endDate", null).single()).data || {}
|
const rows = await staffTime.list({ user_id: profileStore.activeProfile?.user_id || profileStore.activeProfile?.id })
|
||||||
console.log(runningTimeInfo.value)
|
runningTimeInfo.value = rows.find((r) => !r.stopped_at && r.type === "work") || {}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPage()
|
setupPage()
|
||||||
@@ -19,47 +19,25 @@ setupPage()
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
const startTime = async () => {
|
const startTime = async () => {
|
||||||
console.log("started")
|
try {
|
||||||
runningTimeInfo.value = {
|
await staffTime.start("Arbeitszeit")
|
||||||
profile: profileStore.activeProfile.id,
|
toast.add({title: "Anwesenheit erfolgreich gestartet"})
|
||||||
startDate: dayjs(),
|
await setupPage()
|
||||||
tenant: profileStore.currentTenant,
|
} catch (error) {
|
||||||
state: "Im Web gestartet",
|
|
||||||
source: "Dashboard"
|
|
||||||
}
|
|
||||||
|
|
||||||
const {data,error} = await supabase
|
|
||||||
.from("workingtimes")
|
|
||||||
.insert([runningTimeInfo.value])
|
|
||||||
.select()
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
console.log(error)
|
||||||
toast.add({title: "Fehler beim starten der Zeit",color:"rose"})
|
toast.add({title: "Fehler beim starten der Zeit",color:"rose"})
|
||||||
} else if(data) {
|
|
||||||
toast.add({title: "Anwesenheit erfolgreich gestartet"})
|
|
||||||
runningTimeInfo.value = data[0]
|
|
||||||
console.log(runningTimeInfo.value)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopStartedTime = async () => {
|
const stopStartedTime = async () => {
|
||||||
runningTimeInfo.value.endDate = dayjs()
|
try {
|
||||||
runningTimeInfo.value.state = "Im Web gestoppt"
|
await staffTime.stop()
|
||||||
|
|
||||||
const {error,status} = await supabase
|
|
||||||
.from("workingtimes")
|
|
||||||
.update(runningTimeInfo.value)
|
|
||||||
.eq('id',runningTimeInfo.value.id)
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
let errorId = await useError().logError(`${status} - ${JSON.stringify(error)}`)
|
|
||||||
toast.add({title: errorId ? `Fehler beim stoppen der Anwesenheit (Fehler ID: ${errorId})` : `Fehler beim stoppen der Anwesenheit`,color:"rose"})
|
|
||||||
|
|
||||||
|
|
||||||
} else {
|
|
||||||
toast.add({title: "Anwesenheit erfolgreich gestoppt"})
|
toast.add({title: "Anwesenheit erfolgreich gestoppt"})
|
||||||
runningTimeInfo.value = {}
|
runningTimeInfo.value = {}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
let errorId = await useError().logError(`${JSON.stringify(error)}`)
|
||||||
|
toast.add({title: errorId ? `Fehler beim stoppen der Anwesenheit (Fehler ID: ${errorId})` : `Fehler beim stoppen der Anwesenheit`,color:"rose"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,9 +45,9 @@ const stopStartedTime = async () => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div v-if="runningTimeInfo.startDate">
|
<div v-if="runningTimeInfo.started_at">
|
||||||
<p>Start: {{dayjs(runningTimeInfo.startDate).format("HH:mm")}}</p>
|
<p>Start: {{dayjs(runningTimeInfo.started_at).format("HH:mm")}}</p>
|
||||||
<p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.startDate),'minutes') + ' min' }}</p>
|
<p>Dauer: {{dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') > 59 ? `${Math.floor(dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') / 60)}:${dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') % 60} h` : dayjs().diff(dayjs(runningTimeInfo.started_at),'minutes') + ' min' }}</p>
|
||||||
|
|
||||||
<UFormGroup
|
<UFormGroup
|
||||||
class="mt-2"
|
class="mt-2"
|
||||||
@@ -98,4 +76,4 @@ const stopStartedTime = async () => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
247
frontend/components/email/EmailTiptapEditor.vue
Normal file
247
frontend/components/email/EmailTiptapEditor.vue
Normal file
@@ -0,0 +1,247 @@
|
|||||||
|
<template>
|
||||||
|
<div class="email-editor">
|
||||||
|
<div v-if="editor" class="toolbar">
|
||||||
|
<div class="toolbar-group">
|
||||||
|
<button
|
||||||
|
class="toolbar-btn"
|
||||||
|
:class="{ 'is-active': editor.isActive('bold') }"
|
||||||
|
type="button"
|
||||||
|
title="Fett"
|
||||||
|
@click="editor.chain().focus().toggleBold().run()"
|
||||||
|
>
|
||||||
|
B
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="toolbar-btn italic"
|
||||||
|
:class="{ 'is-active': editor.isActive('italic') }"
|
||||||
|
type="button"
|
||||||
|
title="Kursiv"
|
||||||
|
@click="editor.chain().focus().toggleItalic().run()"
|
||||||
|
>
|
||||||
|
I
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="toolbar-btn line-through"
|
||||||
|
:class="{ 'is-active': editor.isActive('strike') }"
|
||||||
|
type="button"
|
||||||
|
title="Durchgestrichen"
|
||||||
|
@click="editor.chain().focus().toggleStrike().run()"
|
||||||
|
>
|
||||||
|
S
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toolbar-group">
|
||||||
|
<button
|
||||||
|
class="toolbar-btn"
|
||||||
|
:class="{ 'is-active': editor.isActive('heading', { level: 2 }) }"
|
||||||
|
type="button"
|
||||||
|
title="Überschrift"
|
||||||
|
@click="editor.chain().focus().toggleHeading({ level: 2 }).run()"
|
||||||
|
>
|
||||||
|
H2
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="toolbar-btn"
|
||||||
|
:class="{ 'is-active': editor.isActive('bulletList') }"
|
||||||
|
type="button"
|
||||||
|
title="Liste"
|
||||||
|
@click="editor.chain().focus().toggleBulletList().run()"
|
||||||
|
>
|
||||||
|
•
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="toolbar-btn"
|
||||||
|
:class="{ 'is-active': editor.isActive('orderedList') }"
|
||||||
|
type="button"
|
||||||
|
title="Nummerierte Liste"
|
||||||
|
@click="editor.chain().focus().toggleOrderedList().run()"
|
||||||
|
>
|
||||||
|
1.
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="toolbar-group">
|
||||||
|
<button
|
||||||
|
class="toolbar-btn"
|
||||||
|
:class="{ 'is-active': editor.isActive('link') }"
|
||||||
|
type="button"
|
||||||
|
title="Link"
|
||||||
|
@click="setLink"
|
||||||
|
>
|
||||||
|
Link
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="toolbar-btn"
|
||||||
|
type="button"
|
||||||
|
title="Link entfernen"
|
||||||
|
@click="unsetLink"
|
||||||
|
>
|
||||||
|
Unlink
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<EditorContent :editor="editor" class="editor-content" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { onBeforeUnmount, watch } from 'vue'
|
||||||
|
import { EditorContent, useEditor } from '@tiptap/vue-3'
|
||||||
|
import StarterKit from '@tiptap/starter-kit'
|
||||||
|
import Placeholder from '@tiptap/extension-placeholder'
|
||||||
|
import Link from '@tiptap/extension-link'
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<{
|
||||||
|
preloadedContent?: string
|
||||||
|
}>(), {
|
||||||
|
preloadedContent: '',
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
updateContent: [payload: { json: Record<string, any>; html: string; text: string }]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emitContent = () => {
|
||||||
|
if (!editor.value) return
|
||||||
|
emit('updateContent', {
|
||||||
|
json: editor.value.getJSON(),
|
||||||
|
html: editor.value.getHTML(),
|
||||||
|
text: editor.value.getText(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const editor = useEditor({
|
||||||
|
content: props.preloadedContent,
|
||||||
|
extensions: [
|
||||||
|
StarterKit,
|
||||||
|
Placeholder.configure({ placeholder: 'E-Mail Inhalt eingeben...' }),
|
||||||
|
Link.configure({ openOnClick: false, autolink: true }),
|
||||||
|
],
|
||||||
|
editorProps: {
|
||||||
|
attributes: {
|
||||||
|
class: 'prosemirror-email',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onCreate: emitContent,
|
||||||
|
onUpdate: emitContent,
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.preloadedContent,
|
||||||
|
(value) => {
|
||||||
|
if (!editor.value) return
|
||||||
|
if (value === editor.value.getHTML()) return
|
||||||
|
editor.value.commands.setContent(value || '', false)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const setLink = () => {
|
||||||
|
if (!editor.value) return
|
||||||
|
const previousUrl = editor.value.getAttributes('link').href
|
||||||
|
const url = window.prompt('URL eingeben:', previousUrl)
|
||||||
|
if (url === null) return
|
||||||
|
if (!url.trim()) {
|
||||||
|
editor.value.chain().focus().extendMarkRange('link').unsetLink().run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
editor.value.chain().focus().extendMarkRange('link').setLink({ href: url }).run()
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsetLink = () => {
|
||||||
|
if (!editor.value) return
|
||||||
|
editor.value.chain().focus().extendMarkRange('link').unsetLink().run()
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
editor.value?.destroy()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.email-editor {
|
||||||
|
border: 1px solid #69c350;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
margin-right: 0.25rem;
|
||||||
|
border-right: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-group:last-child {
|
||||||
|
border-right: 0;
|
||||||
|
margin-right: 0;
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn {
|
||||||
|
min-width: 2rem;
|
||||||
|
height: 2rem;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
background: #fff;
|
||||||
|
color: #374151;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn:hover {
|
||||||
|
background: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toolbar-btn.is-active {
|
||||||
|
background: #dcfce7;
|
||||||
|
border-color: #69c350;
|
||||||
|
color: #14532d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-content {
|
||||||
|
min-height: 320px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.prosemirror-email) {
|
||||||
|
outline: none;
|
||||||
|
min-height: 280px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.prosemirror-email p) {
|
||||||
|
margin: 0.3rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.prosemirror-email ul) {
|
||||||
|
list-style-type: disc;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
margin: 0.4rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.prosemirror-email ol) {
|
||||||
|
list-style-type: decimal;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
margin: 0.4rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.prosemirror-email li) {
|
||||||
|
display: list-item;
|
||||||
|
margin: 0.2rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.prosemirror-email a) {
|
||||||
|
color: #2563eb;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -32,15 +32,7 @@ const default_data = {
|
|||||||
const newProjectDescription = ref(data|| default_data.value);
|
const newProjectDescription = ref(data|| default_data.value);
|
||||||
|
|
||||||
const saveProjectDescription = async () => {
|
const saveProjectDescription = async () => {
|
||||||
//Update Project Description
|
|
||||||
/*const {data:updateData,error:updateError} = await supabase
|
|
||||||
.from("projects")
|
|
||||||
.update({description: newProjectDescription.value})
|
|
||||||
.eq('id',currentProject.id)
|
|
||||||
.select()
|
|
||||||
|
|
||||||
console.log(updateData)
|
|
||||||
console.log(updateError)*/
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -61,4 +53,4 @@ const saveProjectDescription = async () => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,10 +1,20 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
|
|
||||||
const workingtimes = ref([])
|
const workingtimes = ref([])
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
workingtimes.value = (await supabase.from("workingtimes").select().eq("tenant",profileStore.currentTenant).is("endDate",null)).data
|
const profiles = profileStore.profiles || []
|
||||||
|
const checks = await Promise.all(profiles.map(async (profile) => {
|
||||||
|
try {
|
||||||
|
const spans = await useNuxtApp().$api(`/api/staff/time/spans?targetUserId=${profile.user_id || profile.id}`)
|
||||||
|
const openSpan = (spans || []).find((s) => !s.endedAt && s.type === "work")
|
||||||
|
if (openSpan) return { profile: profile.id }
|
||||||
|
} catch (e) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}))
|
||||||
|
workingtimes.value = checks.filter(Boolean)
|
||||||
}
|
}
|
||||||
|
|
||||||
setupPage()
|
setupPage()
|
||||||
@@ -21,4 +31,4 @@ setupPage()
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
item: {
|
item: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
|
|
||||||
export const useErrorLogging = (resourceType) => {
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const toast = useToast()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const logError = async (error) => {
|
|
||||||
let errorData = {
|
|
||||||
message: error,
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
profile: profileStore.activeProfile.id
|
|
||||||
}
|
|
||||||
|
|
||||||
const {data:supabaseData,error:supabaseError} = await supabase.from("errors").insert(errorData).select().single()
|
|
||||||
|
|
||||||
if(supabaseError) {
|
|
||||||
console.error(supabaseError)
|
|
||||||
} else if(supabaseData) {
|
|
||||||
return supabaseData.id
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return { logError}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
|
|
||||||
export const useFiles = () => {
|
export const useFiles = () => {
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import dayjs from "dayjs";
|
|||||||
const baseURL = /*"http://192.168.1.129:3333"*/ /*"http://localhost:3333"*/ "https://functions.fedeo.io"
|
const baseURL = /*"http://192.168.1.129:3333"*/ /*"http://localhost:3333"*/ "https://functions.fedeo.io"
|
||||||
|
|
||||||
export const useFunctions = () => {
|
export const useFunctions = () => {
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const getWorkingTimesEvaluationData = async (user_id, startDate, endDate) => {
|
const getWorkingTimesEvaluationData = async (user_id, startDate, endDate) => {
|
||||||
// Der neue Endpunkt ist /staff/time/evaluation und erwartet die Benutzer-ID als targetUserId Query-Parameter.
|
// Der neue Endpunkt ist /staff/time/evaluation und erwartet die Benutzer-ID als targetUserId Query-Parameter.
|
||||||
@@ -30,26 +29,6 @@ export const useFunctions = () => {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const useCreateTicket = async (subject,message,url,source) => {
|
|
||||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
|
||||||
|
|
||||||
const {data} = await axios({
|
|
||||||
method: "POST",
|
|
||||||
url: `${baseURL}/functions/createticket`,
|
|
||||||
data: {
|
|
||||||
subject,
|
|
||||||
message,
|
|
||||||
source,
|
|
||||||
url
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${access_token}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return !!data.ticket_created;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const useBankingGenerateLink = async (institutionId) => {
|
const useBankingGenerateLink = async (institutionId) => {
|
||||||
return (await useNuxtApp().$api(`/api/banking/link/${institutionId}`)).link
|
return (await useNuxtApp().$api(`/api/banking/link/${institutionId}`)).link
|
||||||
@@ -81,46 +60,8 @@ export const useFunctions = () => {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
const useGetInvoiceData = async (file) => {
|
|
||||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
|
||||||
|
|
||||||
const {data} = await axios({
|
|
||||||
method: "POST",
|
|
||||||
url: `${baseURL}/functions/getinvoicedatafromgpt`,
|
|
||||||
data: {
|
|
||||||
file
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${access_token}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
console.log(data)
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const useSendTelegramNotification = async (message) => {
|
|
||||||
const {data:{session:{access_token}}} = await supabase.auth.getSession()
|
|
||||||
|
|
||||||
const {data,error} = await axios({
|
|
||||||
method: "POST",
|
|
||||||
url: `${baseURL}/functions/sendtelegramnotification`,
|
|
||||||
data: {
|
|
||||||
message: message
|
|
||||||
},
|
|
||||||
headers: {
|
|
||||||
Authorization: `Bearer ${access_token}`
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if(error){
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const useBankingCheckInstitutions = async (bic) => {
|
const useBankingCheckInstitutions = async (bic) => {
|
||||||
|
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
|
|
||||||
export const useNumberRange = (resourceType) => {
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const numberRanges = profileStore.ownTenant.numberRanges
|
|
||||||
|
|
||||||
const numberRange = numberRanges[resourceType]
|
|
||||||
|
|
||||||
const useNextNumber = async () => {
|
|
||||||
|
|
||||||
let nextNumber = numberRange.nextNumber
|
|
||||||
|
|
||||||
let newNumberRanges = numberRanges
|
|
||||||
|
|
||||||
newNumberRanges[resourceType].nextNumber += 1
|
|
||||||
|
|
||||||
const {data,error} = await supabase
|
|
||||||
.from("tenants")
|
|
||||||
.update({numberRanges: newNumberRanges})
|
|
||||||
.eq('id',profileStore.currentTenant)
|
|
||||||
|
|
||||||
|
|
||||||
await profileStore.fetchOwnTenant()
|
|
||||||
|
|
||||||
return (numberRange.prefix ? numberRange.prefix : "") + nextNumber + (numberRange.suffix ? numberRange.suffix : "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return { useNextNumber}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*export const useNumberRange = (resourceType) => {
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const {numberRanges} = storeToRefs(useDataStore())
|
|
||||||
const {fetchNumberRanges} = useDataStore()
|
|
||||||
|
|
||||||
const numberRange = numberRanges.value.find(range => range.resourceType === resourceType)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const useNextNumber = async () => {
|
|
||||||
|
|
||||||
let nextNumber = numberRange.nextNumber
|
|
||||||
|
|
||||||
const {data,error} = await supabase
|
|
||||||
.from("numberranges")
|
|
||||||
.update({nextNumber: nextNumber + 1})
|
|
||||||
.eq('id',numberRange.id)
|
|
||||||
|
|
||||||
fetchNumberRanges()
|
|
||||||
|
|
||||||
return (numberRange.prefix ? numberRange.prefix : "") + nextNumber + (numberRange.suffix ? numberRange.suffix : "")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return { useNextNumber}
|
|
||||||
}*/
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
|
|
||||||
import Handlebars from "handlebars";
|
|
||||||
|
|
||||||
export const usePrintLabel = async (printServerId,printerName , rawZPL ) => {
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
await supabase.from("printJobs").insert({
|
|
||||||
tenant: profileStore.currentTenant,
|
|
||||||
rawContent: rawZPL,
|
|
||||||
printerName: printerName,
|
|
||||||
printServer: printServerId
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useGenerateZPL = (rawZPL,data) => {
|
|
||||||
let template = Handlebars.compile(rawZPL)
|
|
||||||
|
|
||||||
return template(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -41,18 +41,23 @@ export const useStaffTime = () => {
|
|||||||
* aber wir nutzen dafür besser die createEntry Funktion unten.
|
* aber wir nutzen dafür besser die createEntry Funktion unten.
|
||||||
*/
|
*/
|
||||||
const start = async (description = "Arbeitszeit", time?: string) => {
|
const start = async (description = "Arbeitszeit", time?: string) => {
|
||||||
|
|
||||||
|
console.log(auth.user)
|
||||||
|
|
||||||
await $api('/api/staff/time/event', {
|
await $api('/api/staff/time/event', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: {
|
body: {
|
||||||
eventtype: 'work_start',
|
eventtype: 'work_start',
|
||||||
eventtime: time || new Date().toISOString(), // 💡 Fix: Zeit akzeptieren
|
eventtime: time || new Date().toISOString(), // 💡 Fix: Zeit akzeptieren
|
||||||
payload: { description }
|
payload: { description },
|
||||||
|
user_id: auth.user?.id
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const stop = async () => {
|
const stop = async () => {
|
||||||
await $api('/api/staff/time/event', { method: 'POST', body: { eventtype: 'work_end', eventtime: new Date().toISOString() } })
|
await $api('/api/staff/time/event', { method: 'POST', body: { eventtype: 'work_end', eventtime: new Date().toISOString(),
|
||||||
|
user_id: auth.user?.id } })
|
||||||
}
|
}
|
||||||
|
|
||||||
const submit = async (entry: any) => {
|
const submit = async (entry: any) => {
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
|
|
||||||
export const useSum = () => {
|
export const useSum = () => {
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
|
|
||||||
const getIncomingInvoiceSum = (invoice) => {
|
const getIncomingInvoiceSum = (invoice) => {
|
||||||
let sum = 0
|
let sum = 0
|
||||||
invoice.accounts.forEach(account => {
|
invoice.accounts.forEach(account => {
|
||||||
@@ -135,4 +132,4 @@ export const useSum = () => {
|
|||||||
|
|
||||||
return {getIncomingInvoiceSum, getCreatedDocumentSum, getCreatedDocumentSumDetailed, getIsPaid}
|
return {getIncomingInvoiceSum, getCreatedDocumentSum, getCreatedDocumentSumDetailed, getIsPaid}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export default defineNuxtConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
modules: ['@vite-pwa/nuxt','@pinia/nuxt', '@nuxt/ui', '@nuxtjs/supabase', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', '@nuxtjs/leaflet', '@vueuse/nuxt'],
|
modules: ['@vite-pwa/nuxt','@pinia/nuxt', '@nuxt/ui', "nuxt-editorjs", '@nuxtjs/fontaine', 'nuxt-viewport', '@nuxtjs/leaflet', '@vueuse/nuxt'],
|
||||||
|
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|
||||||
@@ -34,12 +34,6 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
supabase: {
|
|
||||||
key: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InV3cHB2Y3hmbHJjc2lidXpzYmlsIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDA5MzgxOTQsImV4cCI6MjAxNjUxNDE5NH0.CkxYSQH0uLfwx9GVUlO6AYMU2FMLAxGMrwEKvyPv7Oo",
|
|
||||||
url: "https://uwppvcxflrcsibuzsbil.supabase.co",
|
|
||||||
redirect: false
|
|
||||||
},
|
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
resolve: {
|
resolve: {
|
||||||
dedupe: [
|
dedupe: [
|
||||||
@@ -160,4 +154,4 @@ export default defineNuxtConfig({
|
|||||||
},
|
},
|
||||||
|
|
||||||
compatibilityDate: '2024-12-18'
|
compatibilityDate: '2024-12-18'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@capacitor/cli": "^7.0.0",
|
"@capacitor/cli": "^7.0.0",
|
||||||
"@nuxtjs/leaflet": "^1.2.3",
|
"@nuxtjs/leaflet": "^1.2.3",
|
||||||
"@nuxtjs/supabase": "^1.1.4",
|
|
||||||
"@vite-pwa/nuxt": "^1.1.0",
|
"@vite-pwa/nuxt": "^1.1.0",
|
||||||
"@vueuse/core": "^14.1.0",
|
"@vueuse/core": "^14.1.0",
|
||||||
"@vueuse/nuxt": "^14.1.0",
|
"@vueuse/nuxt": "^14.1.0",
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ const openBankstatements = () => {
|
|||||||
</UButton>
|
</UButton>
|
||||||
<UButton
|
<UButton
|
||||||
v-if="itemInfo.createddocument"
|
v-if="itemInfo.createddocument"
|
||||||
@click="router.push(`/createDocument/show/${itemInfo.createddocument}`)"
|
@click="router.push(`/createDocument/show/${itemInfo.createddocument.id}`)"
|
||||||
icon="i-heroicons-link"
|
icon="i-heroicons-link"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const showDocument = ref(false)
|
|||||||
const uri = ref("")
|
const uri = ref("")
|
||||||
|
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
letterheads.value = await useSupabaseSelect("letterheads","*")
|
letterheads.value = await useEntities("letterheads").select("*")
|
||||||
|
|
||||||
preloadedContent.value = `<p></p><p></p><p></p>`
|
preloadedContent.value = `<p></p><p></p><p></p>`
|
||||||
}
|
}
|
||||||
@@ -105,4 +105,4 @@ const contentChanged = (content) => {
|
|||||||
.previewDocumentMobile {
|
.previewDocumentMobile {
|
||||||
aspect-ratio: 1 / 1.414;
|
aspect-ratio: 1 / 1.414;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
//TODO: BACKENDCHANGE EMAIL SENDING
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
@@ -38,7 +37,7 @@ const setupPage = async () => {
|
|||||||
if(route.query.to) emailData.value.to = route.query.to
|
if(route.query.to) emailData.value.to = route.query.to
|
||||||
if(route.query.cc) emailData.value.cc = route.query.cc
|
if(route.query.cc) emailData.value.cc = route.query.cc
|
||||||
if(route.query.bcc) emailData.value.bcc = route.query.bcc
|
if(route.query.bcc) emailData.value.bcc = route.query.bcc
|
||||||
if(route.query.subject) emailData.value.to = route.query.subject
|
if(route.query.subject) emailData.value.subject = route.query.subject
|
||||||
|
|
||||||
|
|
||||||
if(route.query.loadDocuments) {
|
if(route.query.loadDocuments) {
|
||||||
@@ -285,7 +284,7 @@ const sendEmail = async () => {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Tiptap
|
<EmailTiptapEditor
|
||||||
class="mt-3"
|
class="mt-3"
|
||||||
@updateContent="contentChanged"
|
@updateContent="contentChanged"
|
||||||
:preloadedContent="preloadedContent"
|
:preloadedContent="preloadedContent"
|
||||||
@@ -346,4 +345,4 @@ const sendEmail = async () => {
|
|||||||
scrollbar-width: none; /* Firefox */
|
scrollbar-width: none; /* Firefox */
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ defineShortcuts({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -38,7 +37,7 @@ const openTab = ref(0)
|
|||||||
//Functions
|
//Functions
|
||||||
const setupPage = async () => {
|
const setupPage = async () => {
|
||||||
if(mode.value === "show" || mode.value === "edit"){
|
if(mode.value === "show" || mode.value === "edit"){
|
||||||
itemInfo.value = await useSupabaseSelectSingle("roles",route.params.id,"*")
|
itemInfo.value = await useEntities("roles").selectSingle(route.params.id,"*")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -195,4 +194,4 @@ td {
|
|||||||
padding-bottom: 0.15em;
|
padding-bottom: 0.15em;
|
||||||
padding-top: 0.15em;
|
padding-top: 0.15em;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
const items = ref([])
|
const items = ref([])
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
items.value = await useSupabaseSelect("roles","*")
|
items.value = await useEntities("roles").select("*")
|
||||||
}
|
}
|
||||||
|
|
||||||
setup()
|
setup()
|
||||||
@@ -19,4 +19,4 @@ setup()
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import axios from "axios"
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
|
|
||||||
const createEMailAddress = ref("")
|
const createEMailAddress = ref("")
|
||||||
const createEMailType = ref("imap")
|
const createEMailType = ref("imap")
|
||||||
@@ -96,4 +92,4 @@ const columns = computed(() => templateColumns.filter((column) => selectedColumn
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -41,7 +41,6 @@ const setupPage = async (sort_column = null, sort_direction = null) => {
|
|||||||
console.log(item.value)
|
console.log(item.value)
|
||||||
} else if (mode.value === "list") {
|
} else if (mode.value === "list") {
|
||||||
//Load Data for List
|
//Load Data for List
|
||||||
//items.value = await useEntities(type).select(dataType.supabaseSelectWithInformation, sort_column || dataType.supabaseSortColumn, sort_direction === "asc", true)
|
|
||||||
items.value = await useEntities(type).select({
|
items.value = await useEntities(type).select({
|
||||||
filters: {},
|
filters: {},
|
||||||
sort: [],
|
sort: [],
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ const pageLimit = ref(15)
|
|||||||
const page = ref(tempStore.pages[type] || 1)
|
const page = ref(tempStore.pages[type] || 1)
|
||||||
|
|
||||||
const sort = ref({
|
const sort = ref({
|
||||||
column: dataType.supabaseSortColumn || "created_at",
|
column: dataType.sortColumn || "created_at",
|
||||||
direction: 'desc'
|
direction: 'desc'
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ const setupPage = async () => {
|
|||||||
|
|
||||||
|
|
||||||
const {data,meta} = await useEntities(type).selectPaginated({
|
const {data,meta} = await useEntities(type).selectPaginated({
|
||||||
select: dataType.supabaseSelectWithInformation || "*",
|
select: dataType.selectWithInformation || "*",
|
||||||
filters: filters,
|
filters: filters,
|
||||||
sort: [{field: sort.value.column, direction: sort.value.direction}],
|
sort: [{field: sort.value.column, direction: sort.value.direction}],
|
||||||
page: page.value,
|
page: page.value,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
import {useFunctions} from "~/composables/useFunctions.js";
|
import {useFunctions} from "~/composables/useFunctions.js";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const profileStore = useProfileStore()
|
const profileStore = useProfileStore()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
@@ -143,4 +142,4 @@ const addMessage = async () => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,15 +10,6 @@ const showClosedTickets = ref(false)
|
|||||||
const selectedTenant = ref(null)
|
const selectedTenant = ref(null)
|
||||||
|
|
||||||
const setup = async () => {
|
const setup = async () => {
|
||||||
/*if(profileStore.currentTenant === 5) {
|
|
||||||
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*), tenant(*)").order("created_at", {ascending: false})).data
|
|
||||||
} else {
|
|
||||||
tickets.value = (await supabase.from("tickets").select("*,created_by(*), ticketmessages(*)").eq("tenant",profileStore.currentTenant).order("created_at", {ascending: false})).data
|
|
||||||
}
|
|
||||||
|
|
||||||
if(profileStore.currentTenant === 5) {
|
|
||||||
tenants.value = (await supabase.from("tenants").select().order("id")).data
|
|
||||||
}*/
|
|
||||||
tickets.value = await useEntities("tickets").select("*,created_by(*), ticketmessages(*)", "created_at", false)
|
tickets.value = await useEntities("tickets").select("*,created_by(*), ticketmessages(*)", "created_at", false)
|
||||||
|
|
||||||
|
|
||||||
@@ -107,4 +98,4 @@ const filteredRows = computed(() => {
|
|||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,500 +0,0 @@
|
|||||||
<script setup>
|
|
||||||
import dayjs from "dayjs";
|
|
||||||
import VueDatePicker from '@vuepic/vue-datepicker'
|
|
||||||
import '@vuepic/vue-datepicker/dist/main.css'
|
|
||||||
import {setPageLayout} from "#app";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const dataStore = useDataStore()
|
|
||||||
const profileStore = useProfileStore()
|
|
||||||
const supabase = useSupabaseClient()
|
|
||||||
const user = useSupabaseUser()
|
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
|
|
||||||
const timeTypes = profileStore.ownTenant.timeConfig.timeTypes
|
|
||||||
const timeInfo = ref({
|
|
||||||
profile: "",
|
|
||||||
startDate: "",
|
|
||||||
endDate: null,
|
|
||||||
notes: null,
|
|
||||||
project: null,
|
|
||||||
type: null
|
|
||||||
})
|
|
||||||
|
|
||||||
const filterUser = ref(profileStore.activeProfile.id || "")
|
|
||||||
|
|
||||||
const times = ref([])
|
|
||||||
const runningTimeInfo = ref({})
|
|
||||||
const showConfigTimeModal = ref(false)
|
|
||||||
const configTimeMode = ref("create")
|
|
||||||
|
|
||||||
const platform = ref("default")
|
|
||||||
|
|
||||||
const projects = ref([])
|
|
||||||
|
|
||||||
const setup = async () => {
|
|
||||||
times.value = await useSupabaseSelect("times","*, profile(*), project(id, name)")
|
|
||||||
projects.value = await useSupabaseSelect("projects","*")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
runningTimeInfo.value = (await supabase
|
|
||||||
.from("times")
|
|
||||||
.select()
|
|
||||||
.eq("tenant", profileStore.currentTenant)
|
|
||||||
.eq("profile", profileStore.activeProfile.id)
|
|
||||||
.is("endDate",null)
|
|
||||||
.single()).data
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
setup()
|
|
||||||
|
|
||||||
const filteredRows = computed(() => {
|
|
||||||
|
|
||||||
//let times = times.value
|
|
||||||
|
|
||||||
/*if(dataStore.hasRight('viewTimes')) {
|
|
||||||
if(filterUser.value !== "") {
|
|
||||||
times = times.filter(i => i.user === filterUser.value)
|
|
||||||
}
|
|
||||||
} else if(dataStore.hasRight('viewOwnTimes')) {
|
|
||||||
times = times.filter(i => i.user === user.value.id)
|
|
||||||
} else {
|
|
||||||
times = []
|
|
||||||
}*/
|
|
||||||
|
|
||||||
return times.value
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const itemInfo = ref({
|
|
||||||
user: "",
|
|
||||||
notes: null,
|
|
||||||
project: null,
|
|
||||||
type: null,
|
|
||||||
state: "Entwurf"
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
const columns = [
|
|
||||||
{
|
|
||||||
key:"state",
|
|
||||||
label: "Status",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "user",
|
|
||||||
label: "Benutzer",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key:"startDate",
|
|
||||||
label:"Start",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "endDate",
|
|
||||||
label: "Ende",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key:"type",
|
|
||||||
label:"Typ",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "project",
|
|
||||||
label: "Projekt",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: "notes",
|
|
||||||
label: "Notizen",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const startTime = async () => {
|
|
||||||
console.log("started")
|
|
||||||
timeInfo.value.profile = profileStore.activeProfile.id
|
|
||||||
timeInfo.value.startDate = dayjs()
|
|
||||||
timeInfo.value.tenant = profileStore.currentTenant
|
|
||||||
|
|
||||||
const {data,error} = await supabase
|
|
||||||
.from("times")
|
|
||||||
.insert([timeInfo.value])
|
|
||||||
.select()
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
toast.add({title: "Fehler beim starten der Zeit",color:"rose"})
|
|
||||||
} else if(data) {
|
|
||||||
toast.add({title: "Zeit erfolgreich gestartet"})
|
|
||||||
runningTimeInfo.value = data[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const stopStartedTime = async () => {
|
|
||||||
runningTimeInfo.value.endDate = dayjs()
|
|
||||||
runningTimeInfo.value.state = "Im Web gestoppt"
|
|
||||||
|
|
||||||
|
|
||||||
const {data,error} = await supabase
|
|
||||||
.from("times")
|
|
||||||
.update(runningTimeInfo.value)
|
|
||||||
.eq('id',runningTimeInfo.value.id)
|
|
||||||
.select()
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else {
|
|
||||||
toast.add({title: "Zeit erfolgreich gestoppt"})
|
|
||||||
runningTimeInfo.value = null
|
|
||||||
setup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const createTime = async () => {
|
|
||||||
const {data,error} = await supabase
|
|
||||||
.from("times")
|
|
||||||
.insert({...itemInfo.value, tenant: profileStore.currentTenant})
|
|
||||||
.select()
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else if(data) {
|
|
||||||
itemInfo.value = {}
|
|
||||||
toast.add({title: "Zeit erfolgreich erstellt"})
|
|
||||||
showConfigTimeModal.value = false
|
|
||||||
setup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const openTime = (row) => {
|
|
||||||
itemInfo.value = row
|
|
||||||
|
|
||||||
itemInfo.value.project = itemInfo.value.project.id
|
|
||||||
itemInfo.value.profile = itemInfo.value.profile.id
|
|
||||||
|
|
||||||
showConfigTimeModal.value = true
|
|
||||||
configTimeMode.value = "edit"
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateTime = async () => {
|
|
||||||
let data = itemInfo.value
|
|
||||||
|
|
||||||
data.profile = data.profile.id
|
|
||||||
data.project = data.project.id
|
|
||||||
|
|
||||||
const {error} = await supabase
|
|
||||||
.from("times")
|
|
||||||
.update(data)
|
|
||||||
.eq('id',itemInfo.value.id)
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
}
|
|
||||||
|
|
||||||
toast.add({title: "Zeit erfolgreich gespeichert"})
|
|
||||||
showConfigTimeModal.value = false
|
|
||||||
setup()
|
|
||||||
}
|
|
||||||
|
|
||||||
const format = (date) => {
|
|
||||||
let dateFormat = dayjs(date).format("DD.MM.YY HH:mm")
|
|
||||||
|
|
||||||
return `${dateFormat}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*const getDuration = (time) => {
|
|
||||||
const dez = dayjs(time.end).diff(time.start,'hour',true).toFixed(2)
|
|
||||||
const hours = Math.floor(dez)
|
|
||||||
const minutes = Math.floor((dez - hours) * 60)
|
|
||||||
return {
|
|
||||||
dezimal: dez,
|
|
||||||
hours: hours,
|
|
||||||
minutes: minutes,
|
|
||||||
composed: `${hours}:${minutes}`
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
const setState = async (newState) => {
|
|
||||||
itemInfo.value.state = newState
|
|
||||||
await updateTime()
|
|
||||||
}
|
|
||||||
|
|
||||||
const getSecondInfo = (item) => {
|
|
||||||
let returnArray = []
|
|
||||||
|
|
||||||
if(item.type) returnArray.push(item.type)
|
|
||||||
if(item.project) returnArray.push(item.project.name)
|
|
||||||
if(item.notes) returnArray.push(item.notes)
|
|
||||||
|
|
||||||
return returnArray
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<UDashboardNavbar title="Projektzeiten">
|
|
||||||
<template #toggle>
|
|
||||||
<div v-if="platform === 'mobile'"></div>
|
|
||||||
</template>
|
|
||||||
</UDashboardNavbar>
|
|
||||||
<UDashboardToolbar>
|
|
||||||
<template #left>
|
|
||||||
<UButton
|
|
||||||
@click="startTime"
|
|
||||||
:disabled="runningTimeInfo "
|
|
||||||
>
|
|
||||||
Start
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="stopStartedTime"
|
|
||||||
:disabled="!runningTimeInfo"
|
|
||||||
>
|
|
||||||
Stop
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
v-if="platform !== 'mobile'"
|
|
||||||
@click="configTimeMode = 'create'; itemInfo = {startDate: new Date(), endDate: new Date()}; showConfigTimeModal = true"
|
|
||||||
>
|
|
||||||
Erstellen
|
|
||||||
</UButton>
|
|
||||||
<USelectMenu
|
|
||||||
v-if="platform !== 'mobile'"
|
|
||||||
:options="profileStore.profiles"
|
|
||||||
option-attribute="fullName"
|
|
||||||
value-attribute="id"
|
|
||||||
v-model="filterUser"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{profileStore.getProfileById(filterUser) ? profileStore.getProfileById(filterUser).fullName : "Kein Benutzer ausgewählt"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</template>
|
|
||||||
</UDashboardToolbar>
|
|
||||||
<div v-if="runningTimeInfo" class="m-3">
|
|
||||||
|
|
||||||
Start: {{dayjs(runningTimeInfo.startDate).format("DD.MM.YY HH:mm")}}
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Notizen:"
|
|
||||||
>
|
|
||||||
<UTextarea
|
|
||||||
v-model="runningTimeInfo.notes"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Projekt:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="dataStore.projects"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="id"
|
|
||||||
v-model="runningTimeInfo.project"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{ dataStore.projects.find(project => project.id === runningTimeInfo.project) ? dataStore.projects.find(project => project.id === runningTimeInfo.project).name : "Projekt auswählen" }}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
|
|
||||||
<UFormGroup
|
|
||||||
label="Kategorie:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="runningTimeInfo.type"
|
|
||||||
:options="timeTypes"
|
|
||||||
option-attribute="label"
|
|
||||||
value-attribute="label"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{runningTimeInfo.type ? runningTimeInfo.type : "Kategorie auswählen"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<UModal
|
|
||||||
v-model="showConfigTimeModal"
|
|
||||||
>
|
|
||||||
<UCard>
|
|
||||||
<template #header>
|
|
||||||
Projektzeit {{configTimeMode === 'create' ? "erstellen" : "bearbeiten"}}
|
|
||||||
</template>
|
|
||||||
<UFormGroup
|
|
||||||
label="Start:"
|
|
||||||
>
|
|
||||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-calendar-days-20-solid"
|
|
||||||
:label="itemInfo.startDate ? dayjs(itemInfo.startDate).format('DD.MM.YYYY HH:mm') : 'Datum auswählen'"
|
|
||||||
variant="outline"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template #panel="{ close }">
|
|
||||||
<LazyDatePicker v-model="itemInfo.startDate" @close="close" mode="dateTime" />
|
|
||||||
</template>
|
|
||||||
</UPopover>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Ende:"
|
|
||||||
>
|
|
||||||
<UPopover :popper="{ placement: 'bottom-start' }">
|
|
||||||
<UButton
|
|
||||||
icon="i-heroicons-calendar-days-20-solid"
|
|
||||||
:label="itemInfo.endDate ? dayjs(itemInfo.endDate).format('DD.MM.YYYY HH:mm') : 'Datum auswählen'"
|
|
||||||
variant="outline"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<template #panel="{ close }">
|
|
||||||
<LazyDatePicker v-model="itemInfo.endDate" @close="close" mode="dateTime" />
|
|
||||||
</template>
|
|
||||||
</UPopover>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Benutzer:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="profileStore.profiles"
|
|
||||||
v-model="itemInfo.profile"
|
|
||||||
option-attribute="fullName"
|
|
||||||
value-attribute="id"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{profileStore.profiles.find(profile => profile.id === itemInfo.profile) ? profileStore.profiles.find(profile => profile.id === itemInfo.profile).fullName : "Benutzer auswählen"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Projekt:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
:options="projects"
|
|
||||||
v-model="itemInfo.project"
|
|
||||||
option-attribute="name"
|
|
||||||
value-attribute="id"
|
|
||||||
searchable
|
|
||||||
searchable-placeholder="Suche..."
|
|
||||||
:search-attributes="['name']"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{dataStore.projects.find(project => project.id === itemInfo.project) ? dataStore.projects.find(project => project.id === itemInfo.project).name : "Projekt auswählen"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Typ:"
|
|
||||||
>
|
|
||||||
<USelectMenu
|
|
||||||
v-model="itemInfo.type"
|
|
||||||
:options="timeTypes"
|
|
||||||
option-attribute="label"
|
|
||||||
value-attribute="label"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
{{itemInfo.type ? itemInfo.type : "Kategorie auswählen"}}
|
|
||||||
</template>
|
|
||||||
</USelectMenu>
|
|
||||||
</UFormGroup>
|
|
||||||
<UFormGroup
|
|
||||||
label="Notizen:"
|
|
||||||
>
|
|
||||||
<UTextarea
|
|
||||||
v-model="itemInfo.notes"
|
|
||||||
/>
|
|
||||||
</UFormGroup>
|
|
||||||
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<InputGroup>
|
|
||||||
<UButton
|
|
||||||
@click="createTime"
|
|
||||||
v-if="configTimeMode === 'create'"
|
|
||||||
>
|
|
||||||
Erstellen
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="updateTime"
|
|
||||||
v-else-if="configTimeMode === 'edit'"
|
|
||||||
>
|
|
||||||
Speichern
|
|
||||||
</UButton>
|
|
||||||
<UButton
|
|
||||||
@click="setState('Eingereicht')"
|
|
||||||
v-if="itemInfo.state === 'Entwurf'"
|
|
||||||
>
|
|
||||||
Einreichen
|
|
||||||
</UButton>
|
|
||||||
</InputGroup>
|
|
||||||
|
|
||||||
</template>
|
|
||||||
</UCard>
|
|
||||||
</UModal>
|
|
||||||
|
|
||||||
<UDashboardPanelContent class="w-full" v-if="platform === 'mobile'">
|
|
||||||
<a
|
|
||||||
v-for="item in filteredRows"
|
|
||||||
class="my-1"
|
|
||||||
>
|
|
||||||
<p class="truncate text-left text-primary text-xl">{{dayjs(item.startDate).format("DD.MM.YYYY HH:mm")}} - {{dayjs(item.endDate).format("HH:mm")}}</p>
|
|
||||||
<p class="text-sm">
|
|
||||||
<span v-for="(i,index) in getSecondInfo(item)">
|
|
||||||
{{i}}{{index < getSecondInfo(item).length - 1 ? " - " : ""}}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
</p>
|
|
||||||
</a>
|
|
||||||
</UDashboardPanelContent>
|
|
||||||
|
|
||||||
|
|
||||||
<UTable
|
|
||||||
v-else
|
|
||||||
class="mt-3"
|
|
||||||
:columns="columns"
|
|
||||||
:rows="filteredRows"
|
|
||||||
:empty-state="{ icon: 'i-heroicons-circle-stack-20-solid', label: 'Noch keine Einträge' }"
|
|
||||||
@select="(row) => openTime(row)"
|
|
||||||
>
|
|
||||||
<template #state-data="{row}">
|
|
||||||
<span
|
|
||||||
v-if="row.state === 'Entwurf'"
|
|
||||||
class="text-rose-500"
|
|
||||||
>{{row.state}}</span>
|
|
||||||
<span
|
|
||||||
v-if="row.state === 'Eingereicht'"
|
|
||||||
class="text-cyan-500"
|
|
||||||
>{{row.state}}</span>
|
|
||||||
<span
|
|
||||||
v-if="row.state === 'Bestätigt'"
|
|
||||||
class="text-primary-500"
|
|
||||||
>{{row.state}}</span>
|
|
||||||
</template>
|
|
||||||
<template #user-data="{row}">
|
|
||||||
{{row.profile ? row.profile.fullName : "" }}
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template #startDate-data="{row}">
|
|
||||||
{{dayjs(row.startDate).format("DD.MM.YY HH:mm")}}
|
|
||||||
</template>
|
|
||||||
<template #endDate-data="{row}">
|
|
||||||
{{dayjs(row.endDate).format("DD.MM.YY HH:mm")}}
|
|
||||||
</template>
|
|
||||||
<template #project-data="{row}">
|
|
||||||
{{row.project ? row.project.name : "" }}
|
|
||||||
</template>
|
|
||||||
</UTable>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import {defineStore} from 'pinia'
|
import {defineStore} from 'pinia'
|
||||||
import dayjs from "dayjs"
|
import dayjs from "dayjs"
|
||||||
//import {typeOf} from "uri-js/dist/esnext/util";
|
//import {typeOf} from "uri-js/dist/esnext/util";
|
||||||
import {useNumberRange} from "~/composables/useNumberRange.js";
|
|
||||||
|
|
||||||
|
|
||||||
import projecttype from "~/components/columnRenderings/projecttype.vue"
|
import projecttype from "~/components/columnRenderings/projecttype.vue"
|
||||||
@@ -57,7 +56,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
redirect: true,
|
redirect: true,
|
||||||
historyItemHolder: "task",
|
historyItemHolder: "task",
|
||||||
supabaseSelectWithInformation: "*, plant(*), project(*), customer(*)",
|
selectWithInformation: "*, plant(*), project(*), customer(*)",
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
name: "Nur Offene Aufgaben",
|
name: "Nur Offene Aufgaben",
|
||||||
@@ -165,8 +164,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
redirect:true,
|
redirect:true,
|
||||||
numberRangeHolder: "customerNumber",
|
numberRangeHolder: "customerNumber",
|
||||||
historyItemHolder: "customer",
|
historyItemHolder: "customer",
|
||||||
supabaseSortColumn: "customerNumber",
|
sortColumn: "customerNumber",
|
||||||
supabaseSelectWithInformation: "*, projects(*), plants(*), contracts(*), contacts(*), createddocuments(*, statementallocations(*)), files(*), events(*)",
|
selectWithInformation: "*, projects(*), plants(*), contracts(*), contacts(*), createddocuments(*, statementallocations(*)), files(*), events(*)",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
default: true,
|
default: true,
|
||||||
@@ -426,7 +425,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
redirect:true,
|
redirect:true,
|
||||||
historyItemHolder: "contact",
|
historyItemHolder: "contact",
|
||||||
supabaseSelectWithInformation: "*, customer(*), vendor(*)",
|
selectWithInformation: "*, customer(*), vendor(*)",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
default: true,
|
default: true,
|
||||||
@@ -566,7 +565,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
"Allgemeines",
|
"Allgemeines",
|
||||||
"Abrechnung"
|
"Abrechnung"
|
||||||
],
|
],
|
||||||
supabaseSelectWithInformation: "*, customer(*), files(*)",
|
selectWithInformation: "*, customer(*), files(*)",
|
||||||
templateColumns: [
|
templateColumns: [
|
||||||
{
|
{
|
||||||
key: 'contractNumber',
|
key: 'contractNumber',
|
||||||
@@ -719,9 +718,9 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
label: "Abwesenheiten",
|
label: "Abwesenheiten",
|
||||||
labelSingle: "Abwesenheit",
|
labelSingle: "Abwesenheit",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
supabaseSortColumn:"startDate",
|
sortColumn:"startDate",
|
||||||
supabaseSortAscending: false,
|
sortAscending: false,
|
||||||
supabaseSelectWithInformation: "*",
|
selectWithInformation: "*",
|
||||||
historyItemHolder: "absencerequest",
|
historyItemHolder: "absencerequest",
|
||||||
redirect:true,
|
redirect:true,
|
||||||
filters:[{
|
filters:[{
|
||||||
@@ -813,8 +812,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
redirect:true,
|
redirect:true,
|
||||||
historyItemHolder: "plant",
|
historyItemHolder: "plant",
|
||||||
supabaseSortColumn:"name",
|
sortColumn:"name",
|
||||||
supabaseSelectWithInformation: "*, customer(id,name)",
|
selectWithInformation: "*, customer(id,name)",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
default: true,
|
default: true,
|
||||||
@@ -869,7 +868,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
labelSingle: "Artikel",
|
labelSingle: "Artikel",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
redirect:true,
|
redirect:true,
|
||||||
supabaseSelectWithInformation: "*, unit(name)",
|
selectWithInformation: "*, unit(name)",
|
||||||
historyItemHolder: "product",
|
historyItemHolder: "product",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
@@ -996,8 +995,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
redirect:true,
|
redirect:true,
|
||||||
historyItemHolder: "project",
|
historyItemHolder: "project",
|
||||||
numberRangeHolder: "projectNumber",
|
numberRangeHolder: "projectNumber",
|
||||||
supabaseSelectWithInformation: "*, customer(id,name), plant(id,name), projecttype(name, id), tasks(*, project(id,name), customer(id,name), plant(id,name)), files(*), createddocuments(*, statementallocations(*)), events(*), times(*, profile(id, fullName))",
|
selectWithInformation: "*, customer(id,name), plant(id,name), projecttype(name, id), tasks(*, project(id,name), customer(id,name), plant(id,name)), files(*), createddocuments(*, statementallocations(*)), events(*), times(*, profile(id, fullName))",
|
||||||
supabaseSortColumn: "projectNumber",
|
sortColumn: "projectNumber",
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
name: "Nur Offene Projekte",
|
name: "Nur Offene Projekte",
|
||||||
@@ -1136,8 +1135,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
redirect:true,
|
redirect:true,
|
||||||
historyItemHolder: "vehicle",
|
historyItemHolder: "vehicle",
|
||||||
supabaseSortColumn:"licensePlate",
|
sortColumn:"licensePlate",
|
||||||
supabaseSelectWithInformation: "*, checks(*), files(*)",
|
selectWithInformation: "*, checks(*), files(*)",
|
||||||
filters:[{
|
filters:[{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
default: true,
|
default: true,
|
||||||
@@ -1243,8 +1242,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
redirect:true,
|
redirect:true,
|
||||||
numberRangeHolder: "vendorNumber",
|
numberRangeHolder: "vendorNumber",
|
||||||
historyItemHolder: "vendor",
|
historyItemHolder: "vendor",
|
||||||
supabaseSortColumn: "vendorNumber",
|
sortColumn: "vendorNumber",
|
||||||
supabaseSelectWithInformation: "*, contacts(*)",
|
selectWithInformation: "*, contacts(*)",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "SEPA nicht erteilt",
|
name: "SEPA nicht erteilt",
|
||||||
"filterFunction": function (row) {
|
"filterFunction": function (row) {
|
||||||
@@ -1395,8 +1394,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
label: "Lagerplätze",
|
label: "Lagerplätze",
|
||||||
labelSingle: "Lagerplatz",
|
labelSingle: "Lagerplatz",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
supabaseSelectWithInformation: "*, files(*)",
|
selectWithInformation: "*, files(*)",
|
||||||
supabaseSortColumn: "spaceNumber",
|
sortColumn: "spaceNumber",
|
||||||
redirect: true,
|
redirect: true,
|
||||||
numberRangeHolder: "spaceNumber",
|
numberRangeHolder: "spaceNumber",
|
||||||
historyItemHolder: "space",
|
historyItemHolder: "space",
|
||||||
@@ -1528,7 +1527,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
isArchivable: true,
|
isArchivable: true,
|
||||||
label: "Dokumente",
|
label: "Dokumente",
|
||||||
labelSingle: "Dokument",
|
labelSingle: "Dokument",
|
||||||
supabaseSelectWithInformation: "*, files(*), statementallocations(*)",
|
selectWithInformation: "*, files(*), statementallocations(*)",
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
@@ -1565,13 +1564,13 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
isArchivable: true,
|
isArchivable: true,
|
||||||
label: "Dateien",
|
label: "Dateien",
|
||||||
labelSingle: "Datei",
|
labelSingle: "Datei",
|
||||||
supabaseSelectWithInformation: "*",
|
selectWithInformation: "*",
|
||||||
},
|
},
|
||||||
folders: {
|
folders: {
|
||||||
isArchivable: true,
|
isArchivable: true,
|
||||||
label: "Ordner",
|
label: "Ordner",
|
||||||
labelSingle: "Ordner",
|
labelSingle: "Ordner",
|
||||||
supabaseSelectWithInformation: "*",
|
selectWithInformation: "*",
|
||||||
},
|
},
|
||||||
incominginvoices: {
|
incominginvoices: {
|
||||||
label: "Eingangsrechnungen",
|
label: "Eingangsrechnungen",
|
||||||
@@ -1637,7 +1636,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
label: "Inventarartikel",
|
label: "Inventarartikel",
|
||||||
labelSingle: "Inventarartikel",
|
labelSingle: "Inventarartikel",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
supabaseSelectWithInformation: "*, files(*), vendor(id,name), currentSpace(id,name)",
|
selectWithInformation: "*, files(*), vendor(id,name), currentSpace(id,name)",
|
||||||
redirect: true,
|
redirect: true,
|
||||||
numberRangeHolder: "articleNumber",
|
numberRangeHolder: "articleNumber",
|
||||||
historyItemHolder: "inventoryitem",
|
historyItemHolder: "inventoryitem",
|
||||||
@@ -1789,7 +1788,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
labelSingle: "Inventarartikelgruppe",
|
labelSingle: "Inventarartikelgruppe",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
historyItemHolder: "inventoryitemgroup",
|
historyItemHolder: "inventoryitemgroup",
|
||||||
supabaseSelectWithInformation: "*",
|
selectWithInformation: "*",
|
||||||
redirect: true,
|
redirect: true,
|
||||||
filters:[{
|
filters:[{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
@@ -1857,7 +1856,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
label: "Dokumentenboxen",
|
label: "Dokumentenboxen",
|
||||||
labelSingle: "Dokumentenbox",
|
labelSingle: "Dokumentenbox",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
supabaseSelectWithInformation: "*, space(*), files(*)",
|
selectWithInformation: "*, space(*), files(*)",
|
||||||
redirect: true,
|
redirect: true,
|
||||||
numberRangeHolder: "key",
|
numberRangeHolder: "key",
|
||||||
historyItemHolder: "documentbox",
|
historyItemHolder: "documentbox",
|
||||||
@@ -1922,7 +1921,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
labelSingle: "Leistung",
|
labelSingle: "Leistung",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
redirect: true,
|
redirect: true,
|
||||||
supabaseSelectWithInformation: "*, unit(*)",
|
selectWithInformation: "*, unit(*)",
|
||||||
historyItemHolder: "service",
|
historyItemHolder: "service",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
@@ -2058,7 +2057,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
labelSingle: "Stundensatz",
|
labelSingle: "Stundensatz",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
redirect: true,
|
redirect: true,
|
||||||
supabaseSelectWithInformation: "*",
|
selectWithInformation: "*",
|
||||||
historyItemHolder: "hourrate",
|
historyItemHolder: "hourrate",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
@@ -2109,7 +2108,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
labelSingle: "Termin",
|
labelSingle: "Termin",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
historyItemHolder: "event",
|
historyItemHolder: "event",
|
||||||
supabaseSelectWithInformation: "*, project(id,name), customer(*)",
|
selectWithInformation: "*, project(id,name), customer(*)",
|
||||||
redirect: true,
|
redirect: true,
|
||||||
filters:[{
|
filters:[{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
@@ -2261,8 +2260,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
labelSingle: "Artikelkategorie",
|
labelSingle: "Artikelkategorie",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
redirect: true,
|
redirect: true,
|
||||||
supabaseSortColumn: "name",
|
sortColumn: "name",
|
||||||
supabaseSelectWithInformation: "*",
|
selectWithInformation: "*",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
default: true,
|
default: true,
|
||||||
@@ -2303,8 +2302,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
labelSingle: "Leistungskategorie",
|
labelSingle: "Leistungskategorie",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
redirect: true,
|
redirect: true,
|
||||||
supabaseSortColumn: "name",
|
sortColumn: "name",
|
||||||
supabaseSelectWithInformation: "*",
|
selectWithInformation: "*",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
default: true,
|
default: true,
|
||||||
@@ -2357,7 +2356,7 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
label: "Überprüfungen",
|
label: "Überprüfungen",
|
||||||
labelSingle: "Überprüfung",
|
labelSingle: "Überprüfung",
|
||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
supabaseSelectWithInformation: "*, vehicle(id,licensePlate), profile(id, fullName), inventoryitem(name), files(*)",
|
selectWithInformation: "*, vehicle(id,licensePlate), profile(id, fullName), inventoryitem(name), files(*)",
|
||||||
redirect: true,
|
redirect: true,
|
||||||
historyItemHolder: "check",
|
historyItemHolder: "check",
|
||||||
filters: [{
|
filters: [{
|
||||||
@@ -2440,8 +2439,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
redirect:true,
|
redirect:true,
|
||||||
numberRangeHolder: "number",
|
numberRangeHolder: "number",
|
||||||
historyItemHolder: "costcentre",
|
historyItemHolder: "costcentre",
|
||||||
supabaseSortColumn: "number",
|
sortColumn: "number",
|
||||||
supabaseSelectWithInformation: "*, project(*), vehicle(*), inventoryitem(*)",
|
selectWithInformation: "*, project(*), vehicle(*), inventoryitem(*)",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
default: true,
|
default: true,
|
||||||
@@ -2520,8 +2519,8 @@ export const useDataStore = defineStore('data', () => {
|
|||||||
isStandardEntity: true,
|
isStandardEntity: true,
|
||||||
redirect:true,
|
redirect:true,
|
||||||
historyItemHolder: "ownaccount",
|
historyItemHolder: "ownaccount",
|
||||||
supabaseSortColumn: "number",
|
sortColumn: "number",
|
||||||
supabaseSelectWithInformation: "*, statementallocations(*, bs_id(*))",
|
selectWithInformation: "*, statementallocations(*, bs_id(*))",
|
||||||
filters: [{
|
filters: [{
|
||||||
name: "Archivierte ausblenden",
|
name: "Archivierte ausblenden",
|
||||||
default: true,
|
default: true,
|
||||||
|
|||||||
@@ -1,152 +1,117 @@
|
|||||||
import {defineStore} from 'pinia'
|
import { defineStore } from "pinia"
|
||||||
import OneSignal from "onesignal-cordova-plugin";
|
import OneSignal from "onesignal-cordova-plugin"
|
||||||
import {Capacitor} from "@capacitor/core";
|
import { Capacitor } from "@capacitor/core"
|
||||||
// @ts-ignore
|
|
||||||
export const useProfileStore = defineStore('profile', () => {
|
|
||||||
|
|
||||||
const supabase = useSupabaseClient()
|
export const useProfileStore = defineStore("profile", () => {
|
||||||
|
const auth = useAuthStore()
|
||||||
const dataStore = useDataStore()
|
const dataStore = useDataStore()
|
||||||
const user = useSupabaseUser()
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const router = useRouter()
|
const tempStore = useTempStore()
|
||||||
|
|
||||||
const loaded = ref(false)
|
const loaded = ref(false)
|
||||||
const showProfileSelection = ref(false)
|
const showProfileSelection = ref(false)
|
||||||
|
|
||||||
const ownTenant = ref({
|
const ownTenant = ref({
|
||||||
calendarConfig: {
|
calendarConfig: { eventTypes: [] },
|
||||||
eventTypes: []
|
timeConfig: { timeTypes: [] },
|
||||||
},
|
tags: { documents: [], products: [] },
|
||||||
timeConfig: {
|
|
||||||
timeTypes: []
|
|
||||||
},
|
|
||||||
tags: {
|
|
||||||
documents: [] ,
|
|
||||||
products: []
|
|
||||||
},
|
|
||||||
measures: []
|
measures: []
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
const profiles = ref([])
|
const profiles = ref([])
|
||||||
const ownProfiles = ref([])
|
const ownProfiles = ref([])
|
||||||
const activeProfile = ref([])
|
const activeProfile = ref(null)
|
||||||
const tenants = ref([])
|
const tenants = ref([])
|
||||||
const currentTenant = ref(null)
|
const currentTenant = ref(null)
|
||||||
|
|
||||||
|
const syncFromAuth = () => {
|
||||||
|
currentTenant.value = auth.activeTenant || null
|
||||||
|
activeProfile.value = auth.profile || null
|
||||||
|
tenants.value = auth.tenants || []
|
||||||
|
}
|
||||||
|
|
||||||
async function initializeData (userId) {
|
async function initializeData() {
|
||||||
|
await auth.fetchMe()
|
||||||
let profileconnections = (await supabase.from("profileconnections").select()).data
|
syncFromAuth()
|
||||||
let profiles = (await supabase.from("profiles").select("*, role(*)")).data
|
|
||||||
let activeProfileConnection = profileconnections.find(i => i.active)
|
|
||||||
if(activeProfileConnection) {
|
|
||||||
if(!false) {
|
|
||||||
toast.add({title: 'Aktives Profil ausgewählt'})
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log("Active Profile selected")
|
|
||||||
activeProfile.value = profiles.find(i => i.id === activeProfileConnection.profile_id)
|
|
||||||
currentTenant.value = activeProfile.value.tenant
|
|
||||||
|
|
||||||
if(Capacitor.getPlatform() === "ios") {
|
|
||||||
OneSignal.initialize("1295d5ff-28f8-46a6-9c62-fe5d090016d7");
|
|
||||||
OneSignal.Location.setShared(false)
|
|
||||||
OneSignal.Notifications.requestPermission();
|
|
||||||
OneSignal.login(activeProfileConnection.user_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (activeProfile.value?.temp_config) {
|
||||||
|
tempStore.setStoredTempConfig(activeProfile.value.temp_config)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentTenant.value) {
|
||||||
await fetchData()
|
await fetchData()
|
||||||
await dataStore.fetchData()
|
await dataStore.fetchData()
|
||||||
|
|
||||||
|
if (Capacitor.getPlatform() === "ios" && activeProfile.value?.user_id) {
|
||||||
|
OneSignal.initialize("1295d5ff-28f8-46a6-9c62-fe5d090016d7")
|
||||||
|
OneSignal.Location.setShared(false)
|
||||||
|
OneSignal.Notifications.requestPermission()
|
||||||
|
OneSignal.login(activeProfile.value.user_id)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
toast.add({title: 'Kein aktives Profil', color: 'orange'})
|
toast.add({ title: "Kein aktiver Tenant", color: "orange" })
|
||||||
await fetchOwnProfiles()
|
|
||||||
await fetchTenants()
|
|
||||||
showProfileSelection.value = true
|
showProfileSelection.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function changeProfile(newActiveProfileId) {
|
async function changeProfile(newActiveTenantId) {
|
||||||
loaded.value = false
|
loaded.value = false
|
||||||
|
await auth.switchTenant(String(newActiveTenantId))
|
||||||
let profileconnections = (await supabase.from("profileconnections").select()).data
|
|
||||||
|
|
||||||
let oldActiveProfileConnection = profileconnections.find(i => i.active)
|
|
||||||
|
|
||||||
const {error} = await supabase.from("profileconnections").update({active: true}).eq("profile_id", newActiveProfileId)
|
|
||||||
|
|
||||||
if(error) {
|
|
||||||
console.log(error)
|
|
||||||
} else {
|
|
||||||
if(oldActiveProfileConnection){
|
|
||||||
const {error} = await supabase.from("profileconnections").update({active: false}).eq("profile_id", oldActiveProfileConnection.profile_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
reloadNuxtApp({
|
|
||||||
path:"/",
|
|
||||||
ttl: 10000
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchData () {
|
async function fetchData() {
|
||||||
await fetchOwnProfiles()
|
await fetchOwnProfiles()
|
||||||
await fetchProfiles()
|
await fetchProfiles()
|
||||||
|
|
||||||
await fetchTenants()
|
await fetchTenants()
|
||||||
await fetchOwnTenant()
|
await fetchOwnTenant()
|
||||||
|
|
||||||
loaded.value = true
|
loaded.value = true
|
||||||
console.log("Finished Loading")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearStore () {
|
function clearStore() {
|
||||||
loaded.value = false
|
loaded.value = false
|
||||||
ownTenant.value = {}
|
ownTenant.value = {}
|
||||||
profiles.value = []
|
profiles.value = []
|
||||||
ownProfiles.value = []
|
ownProfiles.value = []
|
||||||
tenants.value = []
|
tenants.value = []
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchOwnTenant () {
|
async function fetchOwnTenant() {
|
||||||
ownTenant.value = (await supabase.from("tenants").select().eq('id', currentTenant.value).single()).data
|
syncFromAuth()
|
||||||
|
ownTenant.value = auth.activeTenantData || ownTenant.value
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchProfiles () {
|
async function fetchProfiles() {
|
||||||
profiles.value = (await supabase.from("profiles").select().eq("tenant",currentTenant.value).order("lastName")).data
|
try {
|
||||||
|
const res = await useNuxtApp().$api("/api/tenant/profiles")
|
||||||
|
profiles.value = res?.data || []
|
||||||
|
} catch (e) {
|
||||||
|
profiles.value = activeProfile.value ? [activeProfile.value] : []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchOwnProfiles () {
|
async function fetchOwnProfiles() {
|
||||||
let profiles = (await supabase.from("profiles").select().order("tenant")).data
|
ownProfiles.value = profiles.value
|
||||||
let conns = (await supabase.from("profileconnections").select()).data.map(i => i.profile_id)
|
|
||||||
ownProfiles.value = profiles.filter(i => conns.includes(i.id))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchTenants () {
|
async function fetchTenants() {
|
||||||
tenants.value = (await supabase.from("tenants").select().order("id",{ascending: true})).data
|
syncFromAuth()
|
||||||
}
|
}
|
||||||
|
|
||||||
const getOwnProfile = computed(() => {
|
const getOwnProfile = computed(() => {
|
||||||
return profiles.value.find(i => i.id === user.value.id)
|
return activeProfile.value
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const getProfileById = computed(() => (itemId) => {
|
const getProfileById = computed(() => (itemId) => {
|
||||||
return profiles.value.find(item => item.id === itemId)
|
return profiles.value.find((item) => item.id === itemId || item.user_id === itemId)
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
//General
|
|
||||||
currentTenant,
|
currentTenant,
|
||||||
loaded,
|
loaded,
|
||||||
showProfileSelection,
|
showProfileSelection,
|
||||||
ownTenant,
|
ownTenant,
|
||||||
initializeData,
|
initializeData,
|
||||||
changeProfile,
|
changeProfile,
|
||||||
|
|
||||||
//Data
|
|
||||||
profiles,
|
profiles,
|
||||||
ownProfiles,
|
ownProfiles,
|
||||||
activeProfile,
|
activeProfile,
|
||||||
@@ -157,6 +122,4 @@ export const useProfileStore = defineStore('profile', () => {
|
|||||||
getOwnProfile,
|
getOwnProfile,
|
||||||
getProfileById
|
getProfileById
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
|
||||||
})
|
|
||||||
|
|||||||
Reference in New Issue
Block a user